Task.Start / WaitとAsync / Awaitの違いは何ですか?


206

私は何かが足りないかもしれませんが、行うことの違いは何ですか:

public void MyMethod()
{
  Task t = Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();
  UpdateLabelToSayItsComplete();
}

public async void MyMethod()
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  UpdateLabelToSayItsComplete();
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

回答:


395

何かが足りないかもしれません

あなたです。

やって違いは何であるTask.Waitとはawait task

ランチはレストランのウェイターに注文します。注文した瞬間、友人があなたの隣に座って座って会話を始めます。ここで2つの選択肢があります。タスクが完了するまで友達を無視することができます-あなたのスープが到着するまで待ち、あなたが待っている間他には何もできません。または、あなたの友人に応答することができ、あなたの友人が話すのをやめると、ウェイターがあなたにスープを持ってくるでしょう。

Task.Waitタスクが完了するまでブロックします-タスクが完了するまで友達を無視します。awaitメッセージキュー内のメッセージの処理を継続し、タスクが完了すると、「待機した後に中断したところからピックアップしてください」というメッセージをキューに入れます。あなたは友達と話し、会話が途絶えるとスープが届きます。


5
@ronagいいえ、違います。Task10ミリ秒かかるを待つことが実際にTaskスレッドで10時間実行され、10時間全体がブロックされる場合、どのようにしますか?
スビック2013

62
@StrugglingCoder:awaitオペレーターは、そのオペランド評価してからすぐに現在の呼び出し元にタスクを返す以外何もしません。非同期性は作業をスレッドにオフロードすることによってのみ達成できるという考えが頭に浮かびますが、それは誤りです。トーストを見ている料理人を雇わずに、トースターがトースターに入っている間に朝食を作って紙を読むことができます。人々は、トースターの中に隠された糸-労働者-があるはずだとよく言っていますが、トースターを見ると、トーストを見ている人はいないと確信しています。
Eric Lippert

11
@StrugglingCoder:それで、あなたが尋ねる仕事をしているのは誰ですか?おそらく、別のスレッドが作業を行っており、そのスレッドがCPUに割り当てられているため、実際に作業が行われています。おそらく、作業はハードウェアによって行われていて、スレッドはまったくありません。しかし、確かに、ハードウェアにはスレッドが必要です。いいえ。ハードウェアはスレッドのレベルの下に存在します。糸は必要ありません!Stephen Clearyの記事「スレッドはありません」を読むとよいでしょう。
Eric Lippert

6
@StrugglingCoder:さて、質問です。非同期の作業が行われていて、ハードウェアがなく、他のスレッドがないとします。これはどのようにして可能ですか?さて、あなたが待っていたものが一連のWindowsメッセージをキューに入れると仮定します。それぞれが少しの作業を行いますか?今、何が起こりますか?制御をメッセージループに戻し、メッセージループからメッセージを引き出し始め、毎回少しずつ作業を行います。最後に実行されるジョブは「タスクの継続を実行する」です。余分なスレッドはありません!
Eric Lippert

8
@StrugglingCoder:さて、今言ったことについて考えてみましょう。 これがWindowsのしくみです。一連のマウスの動きとボタンのクリックなどを実行します。メッセージはキューに入れられ、順番に処理されます。メッセージごとに少量の作業が行われ、すべての処理が完了すると、システムは続行します。1つのスレッドでの非同期は、すでに慣れているものにすぎません。大きなタスクを小さなビットに分割し、それらをキューに入れ、すべての小さなビットを何らかの順序で実行します。それらの実行のいくつかは、他の作業をキューに入れさせ、人生は続きます。1つのスレッド!
Eric Lippert

121

エリックの答えを示すためにここにいくつかのコードがあります:

public void ButtonClick(object sender, EventArgs e)
{
  Task t = new Task.Factory.StartNew(DoSomethingThatTakesTime);
  t.Wait();  
  //If you press Button2 now you won't see anything in the console 
  //until this task is complete and then the label will be updated!
  UpdateLabelToSayItsComplete();
}

public async void ButtonClick(object sender, EventArgs e)
{
  var result = Task.Factory.StartNew(DoSomethingThatTakesTime);
  await result;
  //If you press Button2 now you will see stuff in the console and 
  //when the long method returns it will update the label!
  UpdateLabelToSayItsComplete();
}

public void Button_2_Click(object sender, EventArgs e)
{
  Console.WriteLine("Button 2 Clicked");
}

private void DoSomethingThatTakesTime()
{
  Thread.Sleep(10000);
}

27
コードの+1(100回読み取るよりも1回実行することをお勧めします)。しかし、「//If you press Button2 now you won't see anything in the console until this task is complete and then the label will be updated!」というフレーズは誤解を招くものです。で、ボタンを押すとt.Wait();、ボタンのクリックイベントハンドラでButtonClick()は、プレス何もすることはできませんし、GUIは、GUIを持つ任意のクリックや相互作用であること、凍結して応答しないので、「このタスクが完了するまで」、コンソールとラベルの更新で何かを見ますは タスク待機が完了するまで失われています
Gennady VaninГеннадийВанин

2
エリックはあなたがタスクAPIの基本を理解していると思います。私はそのコードを見て、「t.Waitタスクが完了するまでメインスレッドでブロックされます」と自分に言います。
マフィンマン

50

この例は、違いを非常に明確に示しています。async / awaitを使用すると、呼び出しスレッドはブロックせず、実行を継続します。

static void Main(string[] args)
{
    WriteOutput("Program Begin");
    // DoAsTask();
    DoAsAsync();
    WriteOutput("Program End");
    Console.ReadLine();
}

static void DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    t.Wait();
    WriteOutput("3 - Task completed with result: " + t.Result);
}

static async Task DoAsAsync()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime);
    WriteOutput("2 - Task started");
    var result = await t;
    WriteOutput("3 - Task completed with result: " + result);
}

static int DoSomethingThatTakesTime()
{
    WriteOutput("A - Started something");
    Thread.Sleep(1000);
    WriteOutput("B - Completed something");
    return 123;
}

static void WriteOutput(string message)
{
    Console.WriteLine("[{0}] {1}", Thread.CurrentThread.ManagedThreadId, message);
}

DoAsTask出力:

[1]プログラムの開始
[1] 1-開始
[1] 2-タスクが開始されました
[3] A-何かを始めました
[3] B-何かを完成させた
[1] 3-タスクが完了し、結果:123
[1]プログラム終了

DoAsAsync出力:

[1]プログラムの開始
[1] 1-開始
[1] 2-タスクが開始されました
[3] A-何かを始めました
[1]プログラム終了
[3] B-何かを完成させた
[3] 3-タスクが完了し、結果:123

更新:スレッドIDを出力に表示することにより、例を改善しました。


4
しかし、私が行う場合:new Task(DoAsTask).Start(); DoAsAsync();の代わりに 私は..ので、どこのawaitの利点があり、同じfunctionaletyを得る
omriman12

1
あなたの提案では、タスクの結果は別の場所、おそらく別の方法またはラムダで評価する必要があります。async-awaitは、非同期コードの追跡を容易にします。これは単なる構文エンハンサーです。
2016

@Mas Program EndがAの後にある理由がわかりません-何かを始めました。私の理解では、それが待機することになると、キーワードプロセスはすぐにメインコンテキストに移動してから戻る必要があります。

@JimmyJimm私の理解からTask.Factory.StartNewはDoSomethingThatTakesTimeを実行する新しいスレッドを起動します。そのため、プログラムの終了またはA-開始のいずれかが最初に実行される保証はありません。
RiaanDP、2016

@JimmyJimm:スレッドIDを表示するようにサンプルを更新しました。ご覧のとおり、「プログラムの終了」と「A-何かを開始」は異なるスレッドで実行されています。したがって、実際には順序は確定的ではありません。
Mas

10

Wait()を使用すると、非同期の可能性があるコードが同期して実行されます。待ってません。

たとえば、asp.net Webアプリケーションがあるとします。UserAは/ getUser / 1エンドポイントを呼び出します。asp.netアプリプールはスレッドプール(Thread1)からスレッドを選択し、このスレッドがhttp呼び出しを行います。Wait()を実行すると、このスレッドはhttp呼び出しが解決されるまでブロックされます。待機中、UserBが/ getUser / 2を呼び出すと、アプリプールは別のスレッド(Thread2)を処理してhttp呼び出しを再度行う必要があります。Wait()によってブロックされたThread1を使用できないため、理由なしに別のスレッドを作成しました(実際には、アプリプールから実際にフェッチされました)。

Thread1でawaitを使用すると、SyncContextがThread1とhttp呼び出しの間の同期を管理します。簡単に言うと、http呼び出しが完了すると通知されます。一方、UserBが/ getUser / 2を呼び出す場合は、再度Thread1を使用してhttp呼び出しを行います。これは、待機がヒットすると解放されたためです。次に、別のリクエストでそれを使用できます。http呼び出しが完了すると(user1またはuser2)、Thread1は結果を取得して呼び出し元(クライアント)に戻ることができます。Thread1は複数のタスクに使用されました。


9

この例では、実際には多くありません。別のスレッド(WCF呼び出しなど)で戻るタスク、またはオペレーティングシステム(ファイルIOなど)への制御を放棄するタスクを待機している場合、awaitはスレッドをブロックしないことにより、システムリソースの使用量を減らします。


3

上記の例では、「TaskCreationOptions.HideScheduler」を使用して、「DoAsTask」メソッドを大幅に変更できます。「DoAsAsync」で発生するため、メソッド自体は非同期ではありません。「Task」値を返し、「async」としてマークされ、いくつかの組み合わせを作成します。これにより、「async / await」を使用するのとまったく同じようにできます。 :

static Task DoAsTask()
{
    WriteOutput("1 - Starting");
    var t = Task.Factory.StartNew<int>(DoSomethingThatTakesTime, TaskCreationOptions.HideScheduler); //<-- HideScheduler do the magic

    TaskCompletionSource<int> tsc = new TaskCompletionSource<int>();
    t.ContinueWith(tsk => tsc.TrySetResult(tsk.Result)); //<-- Set the result to the created Task

    WriteOutput("2 - Task started");

    tsc.Task.ContinueWith(tsk => WriteOutput("3 - Task completed with result: " + tsk.Result)); //<-- Complete the Task
    return tsc.Task;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.