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

时间:2013-09-28 18:20:04

标签: c# multithreading visual-studio-2010 .net-4.0 xamarin

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

我想做的其中一个测试是看看如何创建一个Activity,我通过BackgroundWorker线程在GUI上更新控件 - 具体来说,我有兴趣看看Mono语法与a的不同之处有多么不同常规Windows窗体(C#)语法。

为了测试这一点,我创建了一个针对API Level 17(Android 4.2)的Android应用程序(再次在VS2010中)。该应用程序的一般功能将从_DoWork()BackgroundWorker事件处理程序中更改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();
    }
}

无论价值多少,我都试图以RunOnUiThread()的方式使用Invoke(),就像我在Windows Forms这样使用//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(); })); } } } 一样......

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.Android应用程序将崩溃,其中Debug输出显示以下错误(我只包含了显着的信息)...

RunOnUiThread()

我看了Xamarin's Troubleshooting page说明了

  

Dalvik的JNI层仅支持有限数量的JNI对象   引用在任何给定时间点都有效。当这个限制是   事情破裂了。

     

GREF(全局引用)限制是模拟器中的2000个引用,   和~52000硬件参考。

     

当你看到时,你知道你开始创造太多的GREF   Android调试日志中的消息:

你会在错误日志中注意到我的代码将GREF增加到了2001.

根据上面的故障排除说明,我假设{{1}}正在为while循环的每次迭代创建一个JNI对象。如果是这种情况,为什么会发生这种情况,我该如何安全地从另一个线程写入GUI?

2 个答案:

答案 0 :(得分:2)

RunOnUiThread 排队工作。

用于更新UI的技术是可以的,只是在循环中没有暂停,该队列几乎立即就会发现2000个项目。

我打赌在那里有一个短Thread.Sleep()就好了。

答案 1 :(得分:0)

尝试在此

中编写您的UI更新
Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
{
     // code to execute
});