2015-09-07 48 views
3

在Xamarin Android应用程序中,我有一个Activity在RetainInstance片段中调用异步方法(网络操作),以便操作不停止配置更改。操作完成后,界面被更改,进度对话框消失,新的片段被插入到布局中,等等。C#异步方法在Activity暂停时继续,导致IllegalStateException:无法在onSaveInstanceState后执行此操作

即使活动在配置更改中被破坏并重新创建,它也能正常工作。但是,如果活动在异步方法完成时暂停,则UI操作会抛出异常。如果用户在网络操作运行时关闭屏幕或切换到另一个应用程序,则会发生这种情况。

如果活动未暂停,是否有方法使异步方法正常继续。但是,如果活动已暂停,请等到活动恢复后再继续操作?

或者,处理活动暂停时完成的异步操作的正确方法是什么?

代码:

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) 
+0

您是否尝试过 'RunOnUiThread(()=> {});'? – choper

+0

@choper作为'await'的副作用,它已经在UI Thread上运行。但为了确保,我使用了RunOnUiThread,并引发了相同的异常。 – imgx64

+1

http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx然后阅读本文,在其中描述了如何暂停异步方法 – choper

回答

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

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

相关问题