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. */
}
}
そのため、基本的には非同期コードからの例外を呼び出し元のコードにバブルアップしたい それが全く可能でさえあるならば。
読むのは少々奇妙ですが、はい、例外は呼び出し側のコードにバブルアップします - しかし唯一のもし、あんたが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それはかなり良いです - それはこの魔法を達成するためにコンパイラがとるステップを論じます。
DoFoo()
印を付けるasync
ここに? - zmbawait
Fooがvoidを返すときのFooの呼び出しasync void Foo()
。Type void is not awaitable
? - rism
例外が捕捉されないのは、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
}
}
あなたのコードはあなたがそれがすると思うかもしれないことをしません。非同期メソッドは、メソッドが非同期結果の待機を開始した直後に戻ります。コードが実際にどのように動作しているかを調査するためにトレースを使用するのは有益です。
以下のコードは次のことを行います。
 
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スレッドを持っていて、それが他のことであまり忙しくない場合にのみうまくいきます。
非同期メソッドにvoidの戻り型があると、例外の時系列スタックトレースが失われることに注意することも重要です。次のようにTaskを返すことをお勧めします。デバッグをずっと簡単にします。
public async Task DoFoo()
{
try
{
return await Foo();
}
catch (ProtocolException ex)
{
/* Exception with chronological stack trace */
}
}
例外は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();
}
このブログはあなたの問題をきれいに説明しています非同期のベストプラクティス。
その要点は、非同期メソッドの戻り値としてvoidを使用してはいけないことです。非同期イベントハンドラでない限り、例外をキャッチすることができないため、これは不適切な方法です。
ベストプラクティスは、戻り型をTaskに変更することです。 また、ずっと非同期にコーディングし、すべての非同期メソッドを呼び出し、非同期メソッドから呼び出されるようにしてください。コンソールのMainメソッドを除いて、非同期にすることはできません(C#7.1より前)。
このベストプラクティスを無視すると、GUIおよびASP.NETアプリケーションでデッドロックが発生します。デッドロックが発生するのは、これらのアプリケーションが1つのスレッドしか許可しないコンテキストで実行され、それを非同期スレッドに放棄しないためです。つまり、GUIは同期を取って戻りを待ちますが、asyncメソッドはコンテキストを待ちます(デッドロック)。
この動作はコンソールアプリケーションでは発生しません。スレッドプールのコンテキストで実行されるためです。 asyncメソッドはスケジュールされる予定の別のスレッドに戻ります。これがテストコンソールアプリが動作する理由ですが、他のアプリケーションでも同じ呼び出しがデッドロックするでしょう...