大きな違いの1つは、例外の伝播です。 内部スローされた例外、async Task
この方法は、返さに格納されている取得しTask
たオブジェクトとタスクを介して観察されるまで休止状態のままでawait task
、task.Wait()
、task.Result
またはtask.GetAwaiter().GetResult()
。メソッドの同期部分からスローされた場合でも、この方法で伝播されasync
ます。
次のコードについて考えてみます。ここでOneTestAsync
、とのAnotherTestAsync
動作はまったく異なります。
static async Task OneTestAsync(int n)
{
await Task.Delay(n);
}
static Task AnotherTestAsync(int n)
{
return Task.Delay(n);
}
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
Task task = null;
try
{
task = whatTest(n);
Console.Write("Press enter to continue");
Console.ReadLine();
task.Wait();
}
catch (Exception ex)
{
Console.Write("Error: " + ex.Message);
}
}
を呼び出すとDoTestAsync(OneTestAsync, -2)
、次の出力が生成されます。
Enterキーを押して続行します
エラー:1つ以上のエラーが発生しました。awaitTask.Delay
エラー:2番目
注意してください、私はEnterそれを見るために押す必要がありました。
さて、を呼び出すとDoTestAsync(AnotherTestAsync, -2)
、内部のコードワークフローDoTestAsync
はまったく異なり、出力も異なります。今回、私は押すように頼まれませんでしたEnter:
エラー:値は-1(無限のタイムアウトを意味する)、0、または正の整数のいずれかである必要があります。
パラメータ名:milliaseDelayError:1番目
どちらの場合もTask.Delay(-2)
、パラメータを検証しながら、最初にスローします。これは作り上げのシナリオかもしれませんが、理論的にTask.Delay(1000)
は、たとえば、基盤となるシステムタイマーAPIが失敗した場合にもスローされる可能性があります。
ちなみに、エラー伝播ロジックは(async void
メソッドとは対照async Task
的に)メソッドではまだ異なります。async void
メソッド内で発生した例外は、現在のスレッドに同期コンテキストがあるSynchronizationContext.Post
場合は(を介して)、現在のスレッドの同期コンテキストですぐに再スローされます(SynchronizationContext.Current != null)
。それ以外の場合は、を介して再スローされますThreadPool.QueueUserWorkItem
)。呼び出し元には、同じスタックフレームでこの例外を処理する機会がありません。
TPL例外処理の動作に関する詳細をこことここに投稿しました。
Q:async
非同期Task
ベースではないメソッドのメソッドの例外伝播動作を模倣して、後者が同じスタックフレームにスローされないようにすることは可能ですか?
A:本当に必要な場合は、はい、そのためのトリックがあります:
async Task<int> MethodAsync(int arg)
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
}
Task<int> MethodAsync(int arg)
{
var task = new Task<int>(() =>
{
if (arg < 0)
throw new ArgumentException("arg");
return 42 + arg;
});
task.RunSynchronously(TaskScheduler.Default);
return task;
}
ただし、特定の条件下(スタックの深すぎる場合など)RunSynchronously
でも、非同期で実行される可能性があることに注意してください。
もう1つの注目すべき違いは
、async
/await
バージョンはデフォルト以外の同期コンテキストでデッドロックする傾向があることです。たとえば、WinFormsまたはWPFアプリケーションでは次のものがデッドロックします。
static async Task TestAsync()
{
await Task.Delay(1000);
}
void Form_Load(object sender, EventArgs e)
{
TestAsync().Wait();
}
非同期バージョンに変更すると、デッドロックは発生しません。
Task TestAsync()
{
return Task.Delay(1000);
}
デッドロックの性質は、StephenClearyのブログで詳しく説明されています。
await
/async
を使用しても意味がありません:)