awaitとContinueWithの違い


119

場合誰かが説明できawaitそしてContinueWith次の例では同義かではありません。私は初めてTPLを使用しようとしていますが、すべてのドキュメントを読んでいますが、違いがわかりません。

待ってください:

String webText = await getWebPage(uri);
await parseData(webText);

ContinueWith

Task<String> webText = new Task<String>(() => getWebPage(uri));
Task continue = webText.ContinueWith((task) =>  parseData(task.Result));
webText.Start();
continue.Wait();

特定の状況では、どちらが優先されますか?


3
削除した場合はWait第二の例では、コールを、その後 2つのスニペットは、(主に)と同等になります。
サービー2013


参考:getWebPageメソッドは両方のコードで使用できません。最初のコードではTask<string>戻り型があり、2番目のコードではstring戻り型があります。基本的に、コードはコンパイルされません。-正確に言えば。
Royi Namir

回答:


101

2番目のコードでは、継続が完了するのを同期的に待機しています。最初のバージョンでは、メソッドawaitは、まだ完了していない最初の式に到達するとすぐに呼び出し元に戻ります。

どちらも継続をスケジュールするという点で非常に似ていますが、制御フローが少しでも複雑になるとすぐに、コードawaitはるかに単純になります。さらに、Servyのコメントで述べられているように、タスクを待機すると、例外の集計が「アンラップ」され、通常はエラー処理が簡単になります。また、を使用awaitすると、呼び出しコンテキストで継続が暗黙的にスケジュールされます(を使用しない場合ConfigureAwait)。「手動」で実行できないことはありませんが、を使用するとはるかに簡単awaitです。

私はあなたが両方での操作の少し大きめのシーケンスを実行しようと提案awaitし、Task.ContinueWithそれが本当の目を見張ることができます- 。


2
2つのスニペット間のエラー処理も異なります。それは一般的で、作業が簡単でawaitオーバーContinueWithという点で。
サービー2013

@Servy:はい、その周りに何かを追加します。
Jon Skeet 2013

1
スケジューリングは、もかなり異なっている、すなわち、どのようなコンテキストparseDataで実行されます。
ステファン・クリアリー

awaitを使用すると、呼び出しのコンテキスト継続が暗黙的にスケジュールされると言うとき、その利点と他の状況で何が起こるかを説明できますか?
ハリソン

4
@Harrison:WinFormsアプリを作成していると想像してください。非同期メソッドを作成すると、継続がスケジュールされるため、デフォルトでメソッド内のすべてのコードがUIスレッドで実行されます。継続を実行する場所を指定しないと、デフォルトが何であるかわかりませんが、スレッドプールのスレッドで簡単に実行されてしまう可能性があります。その時点では、UIなどにアクセスできません。 。
ジョンスキート

100

以下は、非同期ソルブを使用した違いとさまざまな問題を説明するために最近使用したコードスニペットのシーケンスです。

GUIベースのアプリケーションに時間のかかるイベントハンドラーがあり、それを非同期にしたいとします。以下は、最初に使用する同期ロジックです。

while (true) {
    string result = LoadNextItem().Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
        break;
    }
}

LoadNextItemはTaskを返し、最終的には検査したい結果を生成します。現在の結果が探しているものである場合は、UIのカウンターの値を更新して、メソッドから戻ります。それ以外の場合は、LoadNextItemからの項目の処理を続行します。

非同期バージョンの最初のアイデア:継続を使用するだけです!とりあえずループ部分は無視しましょう。つまり、何がうまくいかないのでしょうか?

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
});

これで、ブロックしないメソッドができました!代わりにクラッシュします。UIコントロールへの更新はすべてUIスレッドで行われるため、それを考慮する必要があります。ありがたいことに、継続をどのようにスケジュールするかを指定するオプションがあり、このためのデフォルトのオプションがあります。

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

これで、クラッシュしないメソッドができました!代わりに静かに失敗します。継続はそれ自体が個別のタスクであり、そのステータスは先行タスクのステータスとは関係ありません。したがって、LoadNextItemが失敗した場合でも、呼び出し元には正常に完了したタスクのみが表示されます。さて、例外があれば渡してください:

return LoadNextItem().ContinueWith(t => {
    if (t.Exception != null) {
        throw t.Exception.InnerException;
    }
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

すばらしい、これは実際に機能します。単品の場合。では、そのループについてはどうでしょう。結局のところ、元の同期バージョンのロジックと同等のソリューションは次のようになります。

Task AsyncLoop() {
    return AsyncLoopTask().ContinueWith(t =>
        Counter.Value = t.Result,
        TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
    var tcs = new TaskCompletionSource<int>();
    DoIteration(tcs);
    return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
    LoadNextItem().ContinueWith(t => {
        if (t.Exception != null) {
            tcs.TrySetException(t.Exception.InnerException);
        } else if (t.Result.Contains("target")) {
            tcs.TrySetResult(t.Result.Length);
        } else {
            DoIteration(tcs);
        }});
}

または、上記のすべての代わりに、asyncを使用して同じことを行うことができます。

async Task AsyncLoop() {
    while (true) {
        string result = await LoadNextItem();
        if (result.Contains("target")) {
            Counter.Value = result.Length;
            break;
        }
    }
}

それは今ではずっといいですよね?


ありがとう、本当に素晴らしい説明
Elger Mensonides 2014年

これは良い例です
Royi Namir
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.