如何安全地从Xamarin.Android中的不同线程写入GUI

本文关键字:线程 GUI Android Xamarin 何安全 安全 | 更新日期: 2023-09-27 18:33:06

我已经下载了Xamarin的试用版,目前正在使用它(在Visual Studio 2010中(。

我想做的测试之一是了解如何制作一个活动,其中我通过 BackgroundWorker 线程更新 GUI 上的控件 - 具体来说,我有兴趣了解 Mono 语法与常规 Windows 窗体 (C#( 语法相比有何不同。

为了测试这一点,我创建了一个针对API Level 17(Android 4.2(的Android应用程序(同样,在VS2010中(。应用的常规功能将从 _DoWork(( 后台工作线程事件处理程序中更改 EditText 控件的文本值。

这是代码..

//Xamarin.Android app
[Activity(Label = "Cross-thread Test", MainLauncher = true)]
public class Activity1 : Activity
{
    EditText labelDisplay;
    BackgroundWorker bgWorker;
    int counter = 0;
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        // Set our view from the "main" layout resource
        SetContentView(Resource.Layout.Main);
        this.bgWorker = new BackgroundWorker();
        this.bgWorker.WorkerSupportsCancellation = true;
        this.bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
        Button buttonStart = FindViewById<Button>(Resource.Id.buttonStart);
        buttonStart.Click += new EventHandler(buttonStart_Click);
        Button buttonStop = FindViewById<Button>(Resource.Id.buttonStop);
        buttonStop.Click += new EventHandler(buttonStop_Click);
        labelDisplay = FindViewById<EditText>(Resource.Id.labelDisplay);
        labelDisplay.Text = "Click Start";
    }
    void bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        while (true)
        {
            if (this.bgWorker.CancellationPending)
            {
                RunOnUiThread(() => labelDisplay.Text = "Click Start");
                break;
            }
            else
            {
                counter++;
                            // This causes GREF to increase to 2001
                RunOnUiThread(() => labelDisplay.Text = counter.ToString());
            }
        }
    }
    private void buttonStart_Click(object sender, EventArgs e)
    {
        if (this.bgWorker != null && !this.bgWorker.IsBusy)
            this.bgWorker.RunWorkerAsync();
    }
    private void buttonStop_Click(object sender, EventArgs e)
    {
        if (this.bgWorker != null && this.bgWorker.IsBusy)
            this.bgWorker.CancelAsync();
    }
}

无论它的价值如何,我都尝试以类似于我在这样的常规Windows Forms中使用Invoke()的方式使用该RunOnUiThread()......

//Regular Windows Forms app
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
    while (true)
    {
        if (this.bgWorker.CancellationPending)
            break;
        else
        {
            counter++;
            //This isn't possible in a Xamarin.Android app, which is why I'm using RunOnUiThread() instead
            this.Invoke((Action)(() => { this.labelDisplay.Text = counter.ToString(); })); 
        }
    }
}

Xamarin.Android 应用程序将崩溃,调试输出显示以下错误(我只包含值得注意的信息(...

09-28 18:09:39.231 D/dalvikvm(  731): GREF has increased to 1701
09-28 18:09:39.461 D/dalvikvm(  731): GREF has increased to 1801
09-28 18:09:40.192 D/dalvikvm(  731): GREF has increased to 1901
09-28 18:09:40.271 D/dalvikvm(  731): GC_CONCURRENT freed 305K, 7% free 6175K/6599K, paused 3ms+5ms
09-28 18:09:40.531 D/dalvikvm(  731): GREF has increased to 2001
09-28 18:09:40.531 W/dalvikvm(  731): JNI global reference table (0x475fd0) dump:
09-28 18:09:40.531 W/dalvikvm(  731):   Last 10 entries (of 2001):
09-28 18:09:40.531 W/dalvikvm(  731):      2000: 0x40fa4e90 java.lang.NoClassDefFoundError
09-28 18:09:40.541 W/dalvikvm(  731):      1999: 0x40fb2e78 mono.java.lang.RunnableImplementor
.
.
.
09-28 18:09:40.581 E/dalvikvm(  731): Excessive JNI global references (2001) //OOPS!
09-28 18:09:40.581 E/dalvikvm(  731): VM aborting
09-28 18:09:40.581 E/mono-rt (  731): Stacktrace:
09-28 18:09:40.581 E/mono-rt (  731): 
09-28 18:09:40.592 E/mono-rt (  731):   at <unknown> <0xffffffff>
09-28 18:09:40.592 E/mono-rt (  731):   at (wrapper managed-to-native) object.wrapper_native_0x408027e9 (intptr,intptr) <IL 0x00026, 0xffffffff>
09-28 18:09:40.592 E/mono-rt (  731):   at Android.Runtime.JNIEnv.NewGlobalRef (intptr) [0x00000] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.8.2-branch/bdc709d1/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.cs:389

我阅读了 Xamarin 的故障排除页面,其中指出

Dalvik 的 JNI 层仅支持有限数量的 JNI 对象 引用在任何给定时间点都有效。当此限制为 超过,事情就坏了。

GREF(全局引用(限制为模拟器中的 2000 个引用, 和硬件上的 ~52000 个引用。

当您看到 Android 调试日志中的消息如下:

您会在错误日志中注意到我的代码将 GREF 增加到 2001。

根据上面的故障排除说明,我假设RunOnUiThread()正在为 while 循环的每次迭代创建一个 JNI 对象。如果是这种情况,为什么会发生这种情况,我应该怎么做才能安全地从其他线程写入 GUI?

如何安全地从Xamarin.Android中的不同线程写入GUI

RunOnUiThread

工作进行排队

更新 UI 的技术是可以的,只是在循环中没有暂停的情况下,队列几乎立即命中 2000 个项目。

我敢打赌,在那里做一个短暂的Thread.Sleep()就好了......

尝试在此编写UI更新

Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
{
     // code to execute
});