93

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

待つ

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

を続行

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

特定の状況では、一方が他方よりも優先されますか?


  • あなたが削除した場合Wait2番目の例で呼び出すそれから2つのスニペットは(ほとんど)同等です。 - Servy
  • の可能な重複Async awaitキーワードはContinueWithラムダと同等ですか? - Stephen Cleary
  • 参考:あなたのgetWebPageメソッドは両方のコードで使用できません。最初のコードではTask<string>2番目にある間に戻り型string戻り型そのため、基本的にあなたのコードはコンパイルされません。 - 正確に言えば。 - Royi Namir

2 답변


80

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

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

両方を使って、少し大きな操作シーケンスを実装してみることをお勧めします。awaitそしてTask.ContinueWith - それは本当のアイオープナーになることができます。


  • 2つのスニペット間のエラー処理も異なります。一般的に作業が簡単ですawaitオーバーContinueWithその点で。 - Servy
  • @Servy:その通りです。 - Jon Skeet
  • スケジューリングもまた全く異なります。parseDataで実行されます。 - Stephen Cleary
  • @StephenCleary:同意した。繰り返しますが、編集します。 - Jon Skeet
  • @Harrison:あなたがWinFormsアプリを書いていると想像してみてください - 非同期メソッドを書いた場合、デフォルトではメソッド内のすべてのコードがUIスレッドで実行されます。継続を実行する場所を指定しないと、デフォルトが何であるかわかりませんが、スレッドプールスレッドで実行してしまう可能性があります。 UIなどにアクセスする - Jon Skeet

74

これが、非同期ソルブを使用したときの違いとさまざまな問題を説明するために最近使用した一連のコードスニペットです。

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

それは今もっとずっといいですね。


リンクされた質問


関連する質問

最近の質問