324

Microsoft for .NETの非同期CTPを使用して、呼び出し元のメソッドで非同期メソッドによってスローされた例外をキャッチすることは可能ですか?

public async void Foo()
{
    var x = await DoSomethingAsync();

    /* Handle the result, but sometimes an exception might be thrown.
       For example, DoSomethingAsync gets data from the network
       and the data is invalid... a ProtocolException might be thrown. */
}

public void DoFoo()
{
    try
    {
        Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will never be caught.
             Instead when in debug mode, VS2010 will warn and continue.
             The deployed the app will simply crash. */
    }
}

ですから、基本的には、非同期コードからの例外が、それが可能であるとしても、呼び出し元のコードにバブルアップすることを望んでいます。

4

6 に答える 6

303

読むのは少し奇妙ですが、はい、例外は呼び出し元のコードにバブルアップします-ただし、あなたawaitまたはWait()Foo.

public async Task Foo()
{
    var x = await DoSomethingAsync();
}

public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          // The exception will be caught because you've awaited
          // the call in an async method.
    }
}

//or//

public void DoFoo()
{
    try
    {
        Foo().Wait();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've
             waited for the completion of the call. */
    }
} 

Stephen Cleary がAsync/Await - Best Practices in Asynchronous Programming で書いたように:

Async void メソッドには、異なるエラー処理セマンティクスがあります。async Task または async Task メソッドから例外がスローされると、その例外がキャプチャされ、Task オブジェクトに配置されます。async void メソッドには Task オブジェクトがないため、async void メソッドからスローされた例外は、async void メソッドの開始時にアクティブだった SynchronizationContext で直接発生します。

Wait().NET がメソッドを同期的に実行することを決定した場合、使用するとアプリケーションがブロックされる可能性があることに注意してください。

この説明http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptionsは非常に優れています。この魔法を実現するためにコンパイラが実行する手順について説明しています。

于 2011-03-21T20:42:21.470 に答える
80

例外がキャッチされない理由は、Foo() メソッドの戻り値の型が void であるため、await が呼び出されると単純に戻るためです。DoFoo() は Foo の完了を待っていないため、例外ハンドラは使用できません。

メソッドのシグネチャを変更できる場合、これにより、より単純なソリューションが開かれます。次のコードのように、型をFoo()返すように変更してから、できるようにします。TaskDoFoo()await Foo()

public async Task Foo() {
    var x = await DoSomethingThatThrows();
}

public async void DoFoo() {
    try {
        await Foo();
    } catch (ProtocolException ex) {
        // This will catch exceptions from DoSomethingThatThrows
    }
}
于 2013-04-22T23:25:20.950 に答える
20

あなたのコードは、あなたが思っていることをしません。非同期メソッドは、メソッドが非同期結果の待機を開始した直後に戻ります。コードが実際にどのように動作しているかを調査するためにトレースを使用すると、洞察力が得られます。

以下のコードは、次のことを行います。

  • 4 つのタスクを作成する
  • 各タスクは非同期に数値をインクリメントし、インクリメントされた数値を返します
  • 非同期の結果が到着すると、それがトレースされます。

 

static TypeHashes _type = new TypeHashes(typeof(Program));        
private void Run()
{
    TracerConfig.Reset("debugoutput");

    using (Tracer t = new Tracer(_type, "Run"))
    {
        for (int i = 0; i < 4; i++)
        {
            DoSomeThingAsync(i);
        }
    }
    Application.Run();  // Start window message pump to prevent termination
}


private async void DoSomeThingAsync(int i)
{
    using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
    {
        t.Info("Hi in DoSomething {0}",i);
        try
        {
            int result = await Calculate(i);
            t.Info("Got async result: {0}", result);
        }
        catch (ArgumentException ex)
        {
            t.Error("Got argument exception: {0}", ex);
        }
    }
}

Task<int> Calculate(int i)
{
    var t = new Task<int>(() =>
    {
        using (Tracer t2 = new Tracer(_type, "Calculate"))
        {
            if( i % 2 == 0 )
                throw new ArgumentException(String.Format("Even argument {0}", i));
            return i++;
        }
    });
    t.Start();
    return t;
}

痕跡を観察すると

22:25:12.649  02172/02820 {          AsyncTest.Program.Run 
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0    
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1    
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3    
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms   
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads. 
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1    
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms    
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms      
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms      
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106    
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0      
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms   
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms   
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3    
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   

Run メソッドはスレッド 2820 で完了しますが、子スレッドは 1 つしか完了していません (2756)。await メソッドの周りに try/catch を配置すると、通常の方法で例外を「キャッチ」できますが、計算タスクが終了して継続が実行されると、コードは別のスレッドで実行されます。

ApiChangeツールから ApiChange.Api.dll を使用したため、計算メソッドはスローされた例外を自動的にトレースします。トレーシングとリフレクターは、何が起こっているのかを理解するのに大いに役立ちます。スレッド化を取り除くには、独自のバージョンの GetAwaiter BeginAwait と EndAwait を作成し、タスクではなく Lazy などをラップして、独自の拡張メソッド内でトレースすることができます。そうすれば、コンパイラと TPL の機能をよりよく理解できるようになります。

これで、例外を伝播するためのスタック フレームが残っていないため、try/catch で例外を取得する方法がないことがわかります。非同期操作を開始した後、コードがまったく異なることをしている可能性があります。Thread.Sleep を呼び出すか、終了することさえあります。フォアグラウンド スレッドが 1 つ残っている限り、アプリケーションは喜んで非同期タスクを実行し続けます。


非同期操作が完了して UI スレッドにコールバックした後、非同期メソッド内で例外を処理できます。これを行うための推奨される方法は、TaskScheduler.FromSynchronizationContextを使用することです。これは、UI スレッドがあり、他のことであまりビジーでない場合にのみ機能します。

于 2011-03-21T21:49:18.397 に答える
5

非同期メソッドに void 戻り値の型がある場合、例外の時系列のスタック トレースが失われることに注意することも重要です。次のように Task を返すことをお勧めします。デバッグがずっと簡単になります。

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }
于 2014-07-04T12:17:12.133 に答える
4

例外は非同期関数でキャッチできます。

public async void Foo()
{
    try
    {
        var x = await DoSomethingAsync();
        /* Handle the result, but sometimes an exception might be thrown
           For example, DoSomethingAsync get's data from the network
           and the data is invalid... a ProtocolException might be thrown */
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught here */
    }
}

public void DoFoo()
{
    Foo();
}
于 2011-03-21T20:50:02.453 に答える