224

Microsoftから.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. */
    }
}

そのため、基本的には非同期コードからの例外を呼び出し元のコードにバブルアップしたい それが全く可能でさえあるならば。


  • これはあなたに何か助けを与えますか?social.msdn.microsoft.com/Forums/en/async/thread/… - svrist
  • 将来誰かがこれにつまずく場合、非同期/ベストプラクティスを待つ...記事その詳細な説明については、「図2非同期のVoidメソッドからの例外をキャッチで捕捉できない」で説明しています。 "非同期タスクまたは非同期タスク< T>から例外がスローされたときメソッド、その例外がキャプチャされ、タスクオブジェクトに配置されます。非同期voidメソッドでは、Taskオブジェクトはありません。非同期voidメソッドからスローされた例外は、非同期voidメソッドの開始時にアクティブだったSynchronizationContextで直接発生します。" - Mr Moose
  • あなたが使用することができますこのアプローチまたはこの - Tselofan

6 답변


206

読むのは少々奇妙ですが、はい、例外は呼び出し側のコードにバブルアップします - しかし唯一のもし、あんたがawaitまたはWait()への呼び出しFoo

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. */
    }
} 

非同期のvoidメソッドは異なるエラー処理セマンティクスを持ちます。非同期タスクまたは非同期タスクメソッドから例外がスローされると、その例外がキャプチャされてタスクオブジェクトに配置されます。非同期voidメソッドの場合、Taskオブジェクトはありません。したがって、非同期voidメソッドからスローされた例外は、非同期voidメソッドの開始時にアクティブだったSynchronizationContextで直接発生します。 - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

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

この説明http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptionsそれはかなり良いです - それはこの魔法を達成するためにコンパイラがとるステップを論じます。


  • 実際に読むのは簡単です - 実際に起こっていることが本当に複雑であることはわかっています - だから私の脳は私の目を信じないように言っています... - Stuart
  • しないでくださいDoFoo()印を付けるasyncここに? - zmb
  • Foo()メソッドはvoidではなくTaskとしてマークされるべきだと思います。 - Sornii
  • 「ただし、Fooの呼び出しを待つまたはWait()した場合のみ」どのようにできますかawaitFooがvoidを返すときのFooの呼び出しasync void Foo()Type void is not awaitable? - rism
  • 無効なメソッドを待つことはできませんね。 - Hitesh P

64

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

メソッドシグネチャを変更することができれば、これはより簡単な解決策を切り開きます。Foo()typeを返すようにTaskその後DoFoo()できる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
    }
}


  • これは本当に忍び寄ることがあるので、コンパイラによって警告されるべきです。 - GGleGrand

16

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

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

  • 4つのタスクを作成
  • 各タスクは非同期的に数値を増やし、増やされた数を返します
  • 非同期結果が到着するとそれは追跡されます。

&nbsp;

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すると、通常の方法で例外を「キャッチ」できますが、計算タスクが終了してcontiuationが実行されると、コードは別のスレッドで実行されます。

私はからApiChange.Api.dllを使用したため、計算方法は自動的にスローされた例外をトレースしますApiChangeツール。 Tracing and Reflectorは何が起こっているのかを理解するのに非常に役立ちます。スレッドを削除するには、独自のバージョンのGetAwaiter BeginAwaitとEndAwaitを作成し、タスクではなくラップすることができます。あなた自身の拡張メソッドの中を怠惰にトレースします。そうすれば、コンパイラーとTPLが何をするのかをよりよく理解できるようになります。

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


非同期操作が終了してUIスレッドにコールバックした後で、asyncメソッド内で例外を処理できます。これを行うための推奨方法は、TaskScheduler.FromSynchronizationContext。それはあなたがUIスレッドを持っていて、それが他のことであまり忙しくない場合にのみうまくいきます。


4

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

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }


3

例外はasync関数で捉えることができます。

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


  • こんにちは、私は知っていますが、DoFooにその情報が必要なので、UIにその情報を表示することができます。この場合、エンドユーザーツールではなく通信プロトコルをデバッグするためのツールであるため、UIに例外を表示することが重要です。 - TimothyP
  • その場合、コールバックは多くの意味をなします(古き良き非同期デリゲート)。 - Sanjeevakumar Hiremath
  • @ティム:あなたがスロー例外に必要な情報を含める? - Eric J.

1

このブログはあなたの問題をきれいに説明しています非同期のベストプラクティス

その要点は、非同期メソッドの戻り値としてvoidを使用してはいけないことです。非同期イベントハンドラでない限り、例外をキャッチすることができないため、これは不適切な方法です。

ベストプラクティスは、戻り型をTaskに変更することです。 また、ずっと非同期にコーディングし、すべての非同期メソッドを呼び出し、非同期メソッドから呼び出されるようにしてください。コンソールのMainメソッドを除いて、非同期にすることはできません(C#7.1より前)。

このベストプラクティスを無視すると、GUIおよびASP.NETアプリケーションでデッドロックが発生します。デッドロックが発生するのは、これらのアプリケーションが1つのスレッドしか許可しないコンテキストで実行され、それを非同期スレッドに放棄しないためです。つまり、GUIは同期を取って戻りを待ちますが、asyncメソッドはコンテキストを待ちます(デッドロック)。

この動作はコンソールアプリケーションでは発生しません。スレッドプールのコンテキストで実行されるためです。 asyncメソッドはスケジュールされる予定の別のスレッドに戻ります。これがテストコンソールアプリが動作する理由ですが、他のアプリケーションでも同じ呼び出しがデッドロックするでしょう...


  • 「コンソールのMainメソッドを除いて、非同期にすることはできません」。C#7.1以降、Mainは非同期メソッドになりましたリンク - Adam

リンクされた質問


関連する質問

最近の質問