在UI线程中执行的异步方法中的异常使应用程序

时间:2015-07-21 21:11:18

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

当我await关于抛出异常的方法时,尝试/ 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 block不会让我免于例外。

我现在找到了对我有用的唯一方法:

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

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

在调试模式下,一切都很好。

  1. 异常来源的原始中断。 enter image description here
  2.   

    调用堆栈

         

    UI.exe !UI.Steps.ViewModels.SvmConnectionViewModel.Process(System.Threading.CancellationToken   的CancellationToken)
       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(对象   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. 下一个中断发生在方法 ThrowForNonSuccess()NotImplementedException occured: A first chance exception of type 'System.NotImplementedException' occurred in mscorlib.dll中的 TaskAwaiter.cs 中。 enter image description here
    2.   

      调用堆栈

           

      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 cancellationToken)第18行    [恢复异步方法]
        的 mscorlib.dll中!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(对象   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(对象   国家)393行   的 WindowsBase.dll中!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate   callback,object args,int numArgs)第118行未知
        的 WindowsBase.dll中!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(对象   source,System.Delegate方法,对象args,int numArgs,   System.Delegate catchHandler)第41行未知
        的 WindowsBase.dll中!System.Windows.Threading.DispatcherOperation.InvokeImpl()   583行未知
        的 WindowsBase.dll中!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(对象   国家)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)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(对象   o)494行未知   的 WindowsBase.dll中!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate   callback,object args,int numArgs)第111行未知   的 WindowsBase.dll中!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(对象   source,System.Delegate方法,对象args,int numArgs,   System.Delegate catchHandler)第41行未知
        的 WindowsBase.dll中!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority   priority,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)Line   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)Line   2031
         Microsoft.VisualStudio.HostingProcess.Utilities.dll !Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()未知   的 mscorlib.dll中!System.Threading.ThreadHelper.ThreadStart_Context(对象   国家)第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回调,对象   520)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.Task   任务)第66行    [异步通话]
         UI.exe !UI.Commands.AsyncCommand.ExecuteAsync(对象参数)第55行    [异步通话]
         UI.exe !UI.Commands.AsyncCommandBase.Execute(对象参数)第15行

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

        调用堆栈

             

        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令牌)第27行    [恢复异步方法]
          ...

        1. 下一个仅在一种方法上有所不同。
        2.   

          调用堆栈

               

          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任务)第66行    [恢复异步方法]
            ...

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

            最终编辑。

            要了解问题的背景和接受的答案,您需要查看这些文章:

1 个答案:

答案 0 :(得分:4)

  

我不想将异常直接传播回主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决定删除它们之前,评论中曾经有更新的代码,我正在努力更新代码示例,令人惊讶的漫长过程)。如果任务同步完成(异常或成功),则会发生错误;在这种情况下,NotifyTaskCompletion<T>.TaskCompleted将为null

要解决此问题,请从此处更改NotifyTaskCompletion<T>的构造函数:

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

到此:

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