大きな違いの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を使用しても意味がありません:)