2017-04-14 47 views
3

現在我知道這是擺在首位使用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()}"); 
} 

輸出

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). 

例外保持活躍整個等待,即使它被抓住鏈。 我檢查生成的代碼,並沒有給出線索,爲什麼發生這種情況,相關的部分(HandleExceptionAsync狀態機產生MoveNext):

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(); 
    } 

我沒有看到這個被相關的同步上下文無論是(在這種情況下,它是一個控制檯應用程序,所以延期計劃在游泳池),我最好的猜測是有一些調用堆棧操作發生,但我找不到任何有關這方面的好消息。

我會很感激,如果有人可以解釋爲什麼發生這種情況,並解釋文檔如何在CLR實現提供鏈接/編譯器

UPD1:該VS調試器的新增截圖表示主動例外異步,示出了同步沒什麼

異步 Repro inside the VS 2015 debugger

同步 No exception in sync version of the code

+0

'GetExceptionCode'在結構化異常處理程序的過濾器之外有[未定義的行爲](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679356(v = vs.85)。 ASPX)。如果您遇到在被捕獲後導致「活動」異常的場景,請發佈問題的重播。 –

+0

@StephenCleary這個特殊的代碼顯示異常在VS調試器($ exception變量)中處於活動狀態。這不適合你嗎? –

+0

對我而言,「主動」意味着它被拋出(或 - 在擴展使用中 - 已被捕獲)。你是否看到過這樣的行爲,還是僅僅是'$ exception'調試器變量? –

回答

5

如果你把一個斷點Log("HandleExceptionAsync.AfterCatch");,調用堆棧解釋的伎倆:

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) 

見底部框架?即使我們從HandleExceptionAsync進行日誌記錄,我們仍然處於ThrowAsync。這怎麼可能?答案也是在調用堆棧:

mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetException(object exceptionObject) Unknown 

說得簡單,因爲await關鍵字,你HandleExceptionAsync方法切碎如下:

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

void HandleExceptionAsync2() 
{ 
    Log("HandleExceptionAsync.AfterCatch"); 
} 

當然,這是後比更復雜那。事實上,這種方法並沒有被切斷,而是簡單地轉化爲狀態機。但是,對於這個演示,這是明智的等價。

HandleExceptionAsync2需要在ThrowAsync之後執行。因此,HandleExceptionAsync2將作爲延續鏈接。喜歡的東西:

ThrowAsync().ContinueWith(HandleExceptionAsync2); 

(再次,這比複雜得多。我只是簡化的解釋)

「問題」,當運行完成了由ThrowAsync返回的任務:

System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetException(object exceptionObject) 

延續實際上將被內聯,並在同一調用堆棧執行(看到上面的幀)。出於性能原因,這是TPL經常進行的優化。因此,當致電Log("HandleExceptionAsync.AfterCatch");,時,您實際上仍處於ThrowAsync的捕獲區塊中,因此您看到的行爲。

+0

感謝一噸,這是一個非常好的解釋,現在整個事情對我來說是完全有意義的,我正在查看生成的狀態機一段時間,而且我沒有想到繼續會從編譯器catch生成的內部執行! –