C# 异步方法在 Activity 暂停时继续,导致 IllegalStateException:在 onSaveInstanceState 之后无法执行此操作
C# async method continues while Activity is paused, causes IllegalStateException: Can not perform this action after onSaveInstanceState
在 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)
把Async当Sync使用是一种错误的方式。如果需要硬控制,使用事件(activity)和线程(网络操作)。
根据@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
实现(从博客post拼凑):
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;
}
}
在 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)
把Async当Sync使用是一种错误的方式。如果需要硬控制,使用事件(activity)和线程(网络操作)。
根据@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
实现(从博客post拼凑):
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;
}
}