在异步方法中捕获异常后的异常

ill*_*ant 4 c# clr async-await

现在我知道这是一个首先使用的黑客Marshal.GetExceptionCode(),但问题不在于它(Visual Studio调试器也检测到一个活动的异常)

private static async Task TestAsync()
{
    Log("TestAsync.Before");

    await HandleExceptionAsync();

    Log("TestAsync.After");
}

private static async Task HandleExceptionAsync()
{
    try
    {
        Log("HandleExceptionAsync.Try");
        await ThrowAsync();
    }
    catch (InvalidOperationException)
    {
        Log("HandleExceptionAsync.Catch");
    }

    Log("HandleExceptionAsync.AfterCatch");
}

private static async Task ThrowAsync()
{
    await Task.Delay(1000);
    throw new InvalidOperationException("Delayed exception");
}

private static void Log(string step)
{
    Console.WriteLine($"{step}: {Marshal.GetExceptionCode()}");
}
Run Code Online (Sandbox Code Playgroud)

产量

TestAsync.Before: 0
HandleExceptionAsync.Try: 0
Exception thrown: 'System.InvalidOperationException' in Interactive.dll
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.ni.dll
HandleExceptionAsync.Catch: -532462766
HandleExceptionAsync.AfterCatch: -532462766
TestAsync.After: -532462766
The thread 9292 has exited with code 0 (0x0).
Run Code Online (Sandbox Code Playgroud)

该异常在整个等待链中保持活跃,即使它已被捕获. 我检查了生成的代码,它没有给出为什么会发生这种情况的相关部分(MoveNextHandleExceptionAsync状态机生成):

  void IAsyncStateMachine.MoveNext()
  {
    int num1 = this.\u003C\u003E1__state;
    try
    {
      if (num1 == 0)
        ;
      try
      {
        TaskAwaiter awaiter;
        int num2;
        if (num1 != 0)
        {
          Program.Log("HandleExceptionAsync.Try");
          awaiter = Program.ThrowAsync().GetAwaiter();
          if (!awaiter.IsCompleted)
          {
            this.\u003C\u003E1__state = num2 = 0;
            this.\u003C\u003Eu__1 = awaiter;
            Program.\u003CHandleExceptionAsync\u003Ed__1 stateMachine = this;
            this.\u003C\u003Et__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.\u003CHandleExceptionAsync\u003Ed__1>(ref awaiter, ref stateMachine);
            return;
          }
        }
        else
        {
          awaiter = this.\u003C\u003Eu__1;
          this.\u003C\u003Eu__1 = new TaskAwaiter();
          this.\u003C\u003E1__state = num2 = -1;
        }
        awaiter.GetResult();
        awaiter = new TaskAwaiter();
      }
      catch (InvalidOperationException ex)
      {
        Program.Log("HandleExceptionAsync.Catch");
      }
      Program.Log("HandleExceptionAsync.AfterCatch");
    }
    catch (Exception ex)
    {
      this.\u003C\u003E1__state = -2;
      this.\u003C\u003Et__builder.SetException(ex);
      return;
    }
    this.\u003C\u003E1__state = -2;
    this.\u003C\u003Et__builder.SetResult();
  }
Run Code Online (Sandbox Code Playgroud)

我没有看到这与同步上下文有关(在这种情况下它是一个控制台应用程序,因此在池上安排了延续),我最好的猜测是有一些调用堆栈操作发生,但我找不到任何好的信息这个.

如果有人能够解释为什么会这样,我会很感激并提供文档的链接,解释如何在CLR /编译器中实现它

UPD1:添加了VS调试器的屏幕截图,显示异步中的活动异常,没有显示任何同步

异步 Repro里面的VS 2015调试器

同步 同步版本的代码中没有例外

Kev*_*sse 6

如果你设置一个断点Log("HandleExceptionAsync.AfterCatch");,那么callstack解释了这个诀窍:

ConsoleApp1.exe!ConsoleApp1.Program.Log(string step) Line 107   C#
ConsoleApp1.exe!ConsoleApp1.Program.HandleExceptionAsync() Line 95  C#
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(object stateMachine)  Unknown
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown
mscorlib.dll!System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Action action, bool allowInlining, ref System.Threading.Tasks.Task currentTask)    Unknown
mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations()  Unknown
mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() Unknown
mscorlib.dll!System.Threading.Tasks.Task.FinishStageTwo()   Unknown
mscorlib.dll!System.Threading.Tasks.Task.Finish(bool bUserDelegateExecuted) Unknown
mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetException(object exceptionObject) Unknown
mscorlib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Threading.Tasks.VoidTaskResult>.SetException(System.Exception exception) Unknown
mscorlib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception exception)    Unknown
ConsoleApp1.exe!ConsoleApp1.Program.ThrowAsync() Line 101   C#
... (continues until the timer of Task.Delay)
Run Code Online (Sandbox Code Playgroud)

看到底部框架?即使我们正在登录,我们仍然在ThrowAsync中HandleExceptionAsync.怎么可能?答案也在callstack中:

mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetException(object exceptionObject) Unknown
Run Code Online (Sandbox Code Playgroud)

简单来说,由于await关键字,您的HandleExceptionAsync方法如下:

void HandleExceptionAsync1()
{
    Log("HandleExceptionAsync.Try");
}

void HandleExceptionAsync2()
{
    Log("HandleExceptionAsync.AfterCatch");
}
Run Code Online (Sandbox Code Playgroud)

当然,这比这更复杂.实际上,该方法不会被切断,只是简单地转换为状态机.然而,对于这个演示,这是明智的等价物.

HandleExceptionAsync2需要在执行之后执行ThrowAsync.因此,HandleExceptionAsync2将链接作为延续.就像是:

ThrowAsync().ContinueWith(HandleExceptionAsync2);
Run Code Online (Sandbox Code Playgroud)

(再次,这比那复杂得多.我只是简化了解释)

当运行时完成ThrowAsync返回的任务时,"问题"是:

System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetException(object exceptionObject)
Run Code Online (Sandbox Code Playgroud)

实际上将继续内联,并在同一个callstack中执行(参见上面的框架).出于性能原因,这是TPL经常进行的优化.因此,在调用时Log("HandleExceptionAsync.AfterCatch");,你实际上仍然处于catch块中ThrowAsync,因此你看到的行为.