C#异步方法在Activity暂停时继续运行,导致IllegalStateException异常:无法在onSaveInstanceState之后执行此操作。

4
在Xamarin Android应用程序中,我有一个Activity调用一个异步方法(网络操作)在一个RetainInstance片段中,以便操作不会在配置更改时停止。操作完成后,UI被更改,进度对话框被解除,新的片段被插入到布局中等等。
即使Activity在配置更改时被销毁并重新创建,它也可以正常工作。但是,如果Activity在异步方法完成时暂停,UI操作会抛出IllegalStateException: Can not perform this action after onSaveInstanceState异常。如果用户在网络操作运行时关闭屏幕或切换到另一个应用程序,则会发生这种情况。
是否有一种方法可以使异步方法在Activity未暂停时继续正常运行。但是,如果Activity已暂停,请等待Activity恢复后再继续?
或者,处理在Activity暂停时完成的异步操作的正确方法是什么?
代码如下:
using System;
using System.Threading.Tasks;

using Android.App;
using Android.OS;
using Android.Widget;

namespace AsyncDemo {
    [Activity(Label = "AsyncDemo", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity {

        const string fragmentTag = "RetainedFragmentTag";
        const string customFragmentTag = "CustomFragmentTag";
        const string dialogTag = "DialogFragmentTag";

        protected override void OnCreate(Bundle savedInstanceState) {
            base.OnCreate(savedInstanceState);

            SetContentView(Resource.Layout.Main);

            var retainedFragment = FragmentManager.FindFragmentByTag(fragmentTag) as RetainedFragment;

            if (retainedFragment == null) {
                retainedFragment = new RetainedFragment();
                FragmentManager.BeginTransaction()
                    .Add(retainedFragment, fragmentTag)
                    .Commit();
            }

            Button button = FindViewById<Button>(Resource.Id.myButton);
            button.Click += delegate {
                button.Text = "Please wait...";

                var dialogFragment = new DialogFragment(); // Substitute for a progress dialog fragment
                FragmentManager.BeginTransaction()
                    .Add(dialogFragment, dialogTag)
                    .Commit();

                Console.WriteLine("Starting task");

                retainedFragment.doIt();
            };
        }

        void taskFinished() {
            Console.WriteLine("Task finished, updating the UI...");

            var button = FindViewById<Button>(Resource.Id.myButton);
            button.Text = "Task finished";

            var dialogFragment = FragmentManager.FindFragmentByTag(dialogTag) as DialogFragment;
            dialogFragment.Dismiss(); // This throws IllegalStateException

            var customFragment = new CustomFragment();
            FragmentManager.BeginTransaction()
                .Replace(Resource.Id.container, customFragment, customFragmentTag)
                .Commit(); // This also throws IllegalStateException
        }

        class RetainedFragment : Fragment {
            public override void OnCreate(Bundle savedInstanceState) {
                base.OnCreate(savedInstanceState);
                RetainInstance = true;
            }

            public void doIt() {
                doItAsync();    
            }

            public async Task doItAsync() {
                try {
                    await Task.Delay(3000); // substitute for the real operation
                    (Activity as MainActivity).taskFinished();
                } catch (Exception e) {
                    Console.WriteLine(e);
                }
            }

        }
    }
}

记录文件:

Starting task
Task finished, updating the UI...
Java.Lang.IllegalStateException: Exception of type 'Java.Lang.IllegalStateException' was thrown.
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in /Users/builder/data/lanes/1978/f98871a9/source/mono/mcs/class/corlib/System.Runtime.ExceptionServices/ExceptionDispatchInfo.cs:61 
  at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod) [0x00062] in /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:554 
  at Android.App.DialogFragment.Dismiss () [0x00043] in /Users/builder/data/lanes/1978/f98871a9/source/monodroid/src/Mono.Android/platforms/android-22/src/generated/Android.App.DialogFragment.cs:284 
  at AsyncDemo.MainActivity.taskFinished () [0x00039] in /Users/csdvirg/workspaces/xamarin/AsyncDemo/AsyncDemo/MainActivity.cs:52 
  at AsyncDemo.MainActivity+RetainedFragment+<doItAsync>c__async0.MoveNext () [0x00094] in /Users/csdvirg/workspaces/xamarin/AsyncDemo/AsyncDemo/MainActivity.cs:73 
  --- End of managed exception stack trace ---
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1323)
    at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1341)
    at android.app.BackStackRecord.commitInternal(BackStackRecord.java:597)
    at android.app.BackStackRecord.commit(BackStackRecord.java:575)
    at android.app.DialogFragment.dismissInternal(DialogFragment.java:292)
    at android.app.DialogFragment.dismiss(DialogFragment.java:258)
    at mono.java.lang.RunnableImplementor.n_run(Native Method)
    at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:29)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5756)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
    at dalvik.system.NativeStart.main(Native Method)

你尝试过使用“RunOnUiThread(() => { });”吗? - choper
@choper 作为await的副作用,它已经在UI线程上运行了。但为了确保,我使用了RunOnUiThread,并且它抛出了相同的异常。 - imgx64
1
请阅读这篇文章:http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx。其中详细描述了如何暂停异步方法。 - choper
1
你可以尝试查看 PauseToken,它在这个链接中有介绍:https://dev59.com/rWIk5IYBdhLWcg3wE6d8#21712588 - xakpc
2个回答

3

根据@choper和@xakz的评论,我使用了PauseTokenSource,现在它完美地工作。

我修改了RetainedFragment:

class RetainedFragment : Fragment {
    readonly PauseTokenSource pts = new PauseTokenSource();

    public override void OnCreate(Bundle savedInstanceState) {
        base.OnCreate(savedInstanceState);
        RetainInstance = true;
    }

    public override void OnPause() {
        base.OnPause();
        pts.IsPaused = true;
    }

    public override void OnResume() {
        base.OnResume();
        pts.IsPaused = false;
    }

    public void doIt() {
        doItAsync();    
    }

    public async Task doItAsync() {
        try {
            await Task.Delay(3000); // substitute for the real operation
            await pts.Token.WaitWhilePausedAsync();
            (Activity as MainActivity).taskFinished();
        } catch (Exception e) {
            Console.WriteLine(e);
        }
    }
}

PauseTokenSource 实现(从博客文章中整理而来):

public class PauseTokenSource {

    internal static readonly Task s_completedTask = Task.FromResult(true);

    volatile TaskCompletionSource<bool> m_paused;

    #pragma warning disable 420
    public bool IsPaused { 
        get { return m_paused != null; } 
        set { 
            if (value) { 
                Interlocked.CompareExchange(
                    ref m_paused, new TaskCompletionSource<bool>(), null); 
            } else { 
                while (true) { 
                    var tcs = m_paused; 
                    if (tcs == null)
                        return; 
                    if (Interlocked.CompareExchange(ref m_paused, null, tcs) == tcs) { 
                        tcs.SetResult(true); 
                        break; 
                    } 
                } 
            } 
        } 
    }
    #pragma warning restore 420

    public PauseToken Token { get { return new PauseToken(this); } }

    internal Task WaitWhilePausedAsync() { 
        var cur = m_paused; 
        return cur != null ? cur.Task : s_completedTask; 
    }
}

public struct PauseToken {
    readonly PauseTokenSource m_source;

    internal PauseToken(PauseTokenSource source) {
        m_source = source;
    }

    public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }

    public Task WaitWhilePausedAsync() { 
        return IsPaused ? 
            m_source.WaitWhilePausedAsync() : 
            PauseTokenSource.s_completedTask; 
    }
}

0

将异步用作同步是错误的方式。如果需要严格控制,请使用事件(活动)和线程(网络操作)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接