【问题标题】:Exception in async method executing in UI-thread crashes application在 UI 线程中执行的异步方法中的异常使应用程序崩溃
【发布时间】:2015-10-11 12:49:36
【问题描述】:

当我 await 在一个引发异常的方法上时,try/catch 不会使应用程序崩溃。

有一种投掷方法

void CurrentStep.Process(CancellationToken cancellationToken)
{
    throw new Exception();
}

通过ICommand.Execute()的方式从UI线程调用

ProcessCurrentStepCommand = AsyncCommandFactory.Create(async cancellationToken => 
{
    try
    {
        await Task.Run(() => CurrentStep.Process(cancellationToken));
    }
    catch {}

    CurrentStep = CurrentStep.NextStepViewModel;
});

ProcessCurrentStepCommand 绑定到 UI 上的按钮。当我单击按钮时,我的应用程序会中断。 我觉得在 UI 线程上抛出异常是一个普遍的问题,但同时我不明白为什么 catch 块不能将我从异常中拯救出来。

我现在找到了唯一适合我的方法:

await Task.Factory.StartNew(
    action: () => CurrentStep.Process(cancellationToken),
    creationOptions: TaskCreationOptions.LongRunning);

但它看起来很丑。如果我以后忘记了我想对这段代码做什么,我可能会认为我需要清理它并遇到一些异常会导致整个应用程序崩溃。


在调试模式下,一切正常。

  1. 异常源上的原始中断。

调用栈

UI.exe!UI.Steps.ViewModels.SvmConnectionViewModel.Process(System.Threading.CancellationToken cancelToken)
UI.exe!UI.MainViewModel..ctor.AnonymousMethod__1() 第 18 行 mscorlib.dll!System.Threading.Tasks.Task.InnerInvoke() 第 2911 行 mscorlib.dll!System.Threading.Tasks.Task.Execute() 第 2523 行 mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) 第 2888 行 mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 581 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 531 行
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) 第 2853 行 mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) 第 2792 行 mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 第 2729 行
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() 第 830 行 mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 1171号线

  1. TaskAwaiter.cs 方法 ThrowForNonSuccess() 中发生下一个中断:NotImplementedException occured: A first chance exception of type 'System.NotImplementedException' occurred in mscorlib.dll

调用栈

mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task 任务)第 180 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task 任务)第 170 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.GetResult() 第 125 行
UI.exe!UI.MainViewModel..ctor(System.Threading.CancellationToken cancelToken) 第 18 行
[恢复异步方法]
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object stateMachine) 第 1065 行
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 581 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 531 行
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run() 第 1045 行
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.OutputAsyncCausalityEvents>.AnonymousMethod__0() 第 973 行
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke() 第 1085 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__0() 第 301 行
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.ContinuationWrapper.Invoke() 第 1085 行
mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate.AnonymousMethod__3() 第 470 行
mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation..cctor.AnonymousMethod__6(object 状态)第 393 行
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Line 118 Unknown
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object 源,System.Delegate 方法,对象 args,int numArgs, System.Delegate catchHandler) 第 41 行未知
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() 第 583 行未知
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object 状态)第 528 行未知
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 581 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 531 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 状态)第 520 行
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() 第 441 行未知
WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() 第 2227 行未知
WindowsBase.dll!System.Windows.Threading .Dispatcher.WndProcHook(System.IntPtr hwnd,int msg,System.IntPtr wParam,System.IntPtr lParam,ref bool 已处理)第 2480 行未知
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd,int msg,System.IntPtr wParam,System.IntPtr lParam,ref bool 已处理) 第 345 行未知
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) 第 494 行未知 WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) Line 111 Unknown WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object 源,System.Delegate 方法,对象 args,int numArgs, System.Delegate catchHandler) 第 41 行未知
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority 优先级、System.TimeSpan 超时、System.Delegate 方法、对象 args, int numArgs) 第 1447 行未知 WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) 行 398 未知
[本地到托管转换]
[托管到本地转换]
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame 框架)第 2281 行未知
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame 框架)第 369 行未知
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() 第 328 行未知
PresentationFramework.dll!System.Windows .Application.RunDispatcher(对象 忽略)第 2745 行
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window 窗口) 1841 号线 PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window 窗口)第 261 行 PresentationFramework.dll!System.Windows.Application.Run() 第 222 行 UI.exe!UI.App.Main() [本机到托管转换]
mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) 行 2031
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 未知 mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object 状态)第 74 行
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 581 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) 第 531 行
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state) 第 520 行
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() 第 111 行
[异步调用]
UI.exe!UI.Commands.AsyncCommandFactory.Create(System.Threading.CancellationToken 令牌)第 27 行
[异步调用]
UI.exe!UI.NotifyTaskCompletion.WatchTaskAsync(System.Threading.Tasks 。任务 任务)第 66 行
[异步调用]
UI.exe!UI.Commands.AsyncCommand.ExecuteAsync(对象参数)第 55 行
[异步调用]
UI.exe!UI.Commands.AsyncCommandBase.Execute(object parameter) 第 15 行

  1. 下一个 break 具有相同的异常并且与上一个堆栈跟踪几乎相同(区别仅在于最后调用的 4 个方法中的第一个)。

调用栈

mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task 任务)第 180 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task 任务)第 170 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.GetResult() 第 125 行
UI.exe!UI.Commands.AsyncCommandFactory.Create(System.Threading.CancellationToken token) 第 27 行
[恢复异步方法]
...

  1. 下一个再次只是在一种方法上有所不同。

调用栈

mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task 任务)第 180 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task 任务)第 170 行
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.GetResult() 第 125 行
UI.exe!UI.NotifyTaskCompletion.WatchTaskAsync(System.Threading.Tasks.Task task) 第 66 行
[恢复异步方法]
...

  1. 在上一次中断之后,不再引发异常,并且 Task.IsFaulted 设置为 true。现在 UI 使用此绑定成功显示异常消息:
<Label Content="{Binding ProcessCurrentStepCommand.Execution.ErrorMessage}" Visibility="{Binding ProcessCurrentStepCommand.Execution.IsFaulted, Converter={StaticResource BooleanToVisibilityConverter}}" />

最终编辑。

要了解问题的上下文和接受的答案,您需要查看以下文章:

【问题讨论】:

  • 抛出的异常是什么?你有抛出异常的堆栈跟踪吗?
  • 当我在调试模式下启动它时,它的行为会有所不同。我将编辑我的答案以包含这些堆栈跟踪。
  • @Pixar:catch 区块正在捕捉到它。只是调试器先看到它并通知你,仅此而已。
  • @Stephen:是的,我明白这一点。我已经包含了所有这些堆栈跟踪来诊断问题。很高兴您能看到我的问题,因为我使用您的文章创建了异步命令 :) 现在我可以更具体地介绍您的示例 AsyncCommand1、AsyncCommand2、AsyncCommand3 和 AsyncCommand4。如果您在 MyService.DownloadAndCountBytesAsync() 方法的开头添加throw new Exception(),当您按下Go 按钮时,应用程序也会崩溃。
  • @Pixar:是的。命令在概念上类似于事件,如果您从任何事件处理程序(或命令)抛出异常,它将被视为顶级应用程序异常,默认行为为崩溃。您期望的行为是什么?

标签: wpf exception async-await task-parallel-library ui-thread


【解决方案1】:

我不想将异常直接传播回主 UI 循环;我想捕获任何异常并设置属性,以便通过数据绑定完成错误处理。

在这种情况下,您真正​​需要的是一个同步命令,它只是启动异步操作,其中异步操作使用NotifyTaskCompletion(或类似的输入你写的)。将这样的操作分解(分为同步“启动”和异步数据绑定)比尝试将所有操作合二为一更容易(这也是可能的 - 只是代码不那么短或可重用):

// Represents the execution of the current step.
NotifyTaskCompletion ProcessCurrentStepCommandExecution
{
  get { return _processCurrentStepCommandExecution; }
  set { _processCurrentStepCommandExecution = value; PropertyChanged(); }
}

...

var cancellationToken = ...; // Wherever you get this from.
ProcessCurrentStepCommand = new DelegateCommand(() =>
{
  ProcessCurrentStepCommandExecution = new NotifyTaskCompletion(async () =>
  {
    await Task.Run(() => CurrentStep.Process(cancellationToken));

    // I'm assuming here you only want to move to the next step if there are no errors.
    // Otherwise, this should be in a finally block.
    CurrentStep = CurrentStep.NextStepViewModel;
  });
});

编辑:

我相信您可能会在该文章的示例代码中遇到错误(在 MSDNMag 决定将它们全部删除之前,cmets 中的代码已经更新,我正在努力更新代码示例,a令人惊讶的漫长过程)。如果任务同步完成(异常或成功),则会出现该错误;在这种情况下,NotifyTaskCompletion&lt;T&gt;.TaskCompleted 将是 null

要解决此问题,请更改 NotifyTaskCompletion&lt;T&gt; 的构造函数:

{
    Task = task;
    if (!task.IsCompleted)
        TaskCompletion = WatchTaskAsync(task);
}

到这里:

{
    Task = task;
    TaskCompletion = WatchTaskAsync(task);
}

【讨论】:

  • 1.这句话来自您的article。你为什么不使用await Task.Run(...)here? 2.如果我throw new Exception()Task.Run(...)中,它仍然会粉碎。 3. 也许最好看看你会对文章Async Programming : Patterns for Asynchronous MVVM Applications: Commands 中的AsyncCommand[i] 项目中的代码做哪些更改? :)
  • @Pixar: Task.Run 用于将 CPU 密集型工作推离 UI 线程。我将其包含在此答案中,因为它在您的代码中;它没有包含在我的文章中,因为我只在那里进行基于 I/O 的操作。
  • 如果我们在 I/O 绑定方法 MyService.DownloadAndCountBytesAsync() 的开头插入命令 throw new Exception() 就在 await Task.Delay(TimeSpan.FromSeconds(3), token).ConfigureAwait(false); 之前,应用程序 AsyncCommand[i] 将崩溃。很难想象 Task.Delay 可能会抛出,但可以想象它是更复杂的方法,例如,它可能会在第一个 await 之前除以 0。
  • @Pixar:在await 之前还是之后都没有区别。失败的AsyncCommand 任务被视为从同步事件处理程序中抛出异常。如果要对异常进行数据绑定,请使用NotifyTaskCompletion
  • 我正在使用它Execution = new NotifyTaskCompletion&lt;TResult&gt;(_command(_cancelCommand.Token));。这就是这个问题的全部意义:) 在您的article 中,您也在使用NotifyTaskCompletion,如果Task.Delay 抛出异常,它将崩溃。您将如何更改您的示例以防止它们出现这种情况?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-09
  • 2015-09-22
  • 1970-01-01
  • 1970-01-01
  • 2018-09-08
相关资源
最近更新 更多