Task.Runをメソッドに入れて非同期にする必要がありますか?


304

私は非同期待機を最も単純な形で理解しようとしています。この例のために2つの数値を追加する非常に単純なメソッドを作成したいと思います。当然のことながら、処理時間はまったくありません。ここで例を作成するだけです。

例1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

例2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

待機DoWork1Async()すると、コードは同期または非同期で実行されますか?

Task.RunUIスレッドをブロックしないように、メソッドを待機可能かつ非同期にするために同期コードをラップする必要がありますか?

私のメソッドがaであるTaskかreturn であるかを理解しようとしていますTask<T>が、コードをラップしTask.Runて非同期にする必要があります。

馬鹿げた質問だと思いますが、ネット上で非同期Task.Runまたはa やにラップされていないコードを待っている人々の例を目にしStartNewます。


30
最初のスニペットは警告を出していませんか?
2013年

回答:


587

最初に、いくつかの用語を明確にします。「非同期」(async)は、開始前に呼び出しスレッドに制御を戻す可能性があることを意味します。async方法、それらの「収率」点であるawait式。

これは、MSDNのドキュメントで長年「バックグラウンドスレッドで実行する」という意味で(誤って)使用されている「非同期」という用語とは大きく異なります。

問題をさらに混乱させることasyncは、「待機可能」とは非常に異なります。いくつかあるasyncではありませんawaitable型を返すその戻り値の型awaitableされていないメソッド、および多くの方法がasync

彼らがそうでないものについては十分です。それら次のとおりです。

  • asyncキーワードは、(それが可能に、ある非同期メソッドができますawait表現を)。async方法は返すことがありTaskTask<T>(あなたがしなければならない場合)、またはvoid
  • 特定のパターンに従う任意のタイプを待つことができます。最も一般的な待機可能なタイプはTaskおよびTask<T>です。

したがって、「バックグラウンドスレッドで待機可能な方法で操作を実行するにはどうすればよいですか」という質問に再定式化した場合、答えは次のようになりますTask.Run

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(しかし、このパターンは貧弱なアプローチです。以下を参照してください)。

しかし、あなたの質問が「asyncブロックする代わりに呼び出し元に戻ることができるメソッドをどのように作成するか」である場合、答えはメソッドを宣言し、その「譲る」ポイントにasync使用することawaitです。

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

ですから、基本的なパターンは、asyncコードがそのawait式の中で "awaitables"に依存するようにすることです。これらの「待機可能物」は、他のasync方法でも、待機可能物を返す通常の方法でもかまいません。返す正規の方法はTask/ Task<T> ことができます使用してTask.Runバックグラウンドスレッドでコードを実行する、または(より一般的に)彼らが使用することができますTaskCompletionSource<T>またはそのショートカット(の1 TaskFactory.FromAsyncTask.FromResultなど)。私はしていないで、全体の方法を包むお勧めしますTask.Run。同期メソッドには同期シグネチャが必要であり、それをTask.Run:でラップするかどうかはコンシューマに任せる必要があります。

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

私のブログにasync/ awaitイントロがあります。最後にいくつかの良いフォローアップリソースがあります。のMSDNドキュメントasyncも非常に優れています。


8
@sgnsajgon:はい。async方法は返さなければならないTaskTask<T>またはvoidTaskTask<T>待っています。voidではありません。
Stephen Cleary 2014

3
実際、async voidメソッドシグネチャはコンパイルされます。非同期タスクへのポインタを
失う

4
@TopinFrassi:はい、コンパイルされますが、void待つことはできません。
スティーブンクリアリー2014年

4
@ohadinho:いいえ、私がブログの投稿で話しているのは、メソッド全体がTask.RunDoWorkAsyncこの回答のように)呼び出しだけの場合です。を使用Task.RunしてUIコンテキストからメソッドを呼び出すことは適切です(などDoVariousThingsFromTheUIThreadAsync)。
スティーブンクリアリー2017

2
はい、正確に。メソッドの呼び出しに使用Task.Runすることは有効ですTask.Runが、メソッドのコードのほぼすべて(またはほぼすべて)が存在する場合、それはアンチパターンTask.Runです。そのメソッドを同期させ、レベルを上げるだけです。
スティーブンクリアリー2017年

22

メソッドをasyncで装飾するときに覚えておくべき最も重要なことの1つは、少なくともメソッド内に待機演算子が1つ あることです。あなたの例では、TaskCompletionSourceを使用して以下のように変換します

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async void DoWork()
{
    int result = await DoWorkAsync();
}

26
Task.Run()メソッドによって返されたタスクを返すだけでなく、結果を返すようにボディを変更するのではなく、なぜTaskCompletionSourceを使用するのですか?
皮肉なことに

4
余談です。「async void」シグニチャーを持つメソッドは、UIのデッドロックに非常に簡単につながる可能性があるため、一般に悪い習慣であり、悪いコードと見なされています。主な例外は非同期イベントハンドラです。
Jazzeroki

12

Task.Runを使用してメソッドを実行すると、Taskはスレッドプールからスレッドを取得して、そのメソッドを実行します。したがって、UIスレッドの観点からは、UIスレッドをブロックしないため、「非同期」です。通常、ユーザーインタラクションを処理するために多くのスレッドを必要としないため、これはデスクトップアプリケーションに適しています。

ただし、Webアプリケーションの場合、各要求はスレッドプールスレッドによって処理されるため、そのようなスレッドを保存することでアクティブな要求の数を増やすことができます。スレッドプールスレッドを頻繁に使用して非同期操作をシミュレートすることは、Webアプリケーションではスケーラブルではありません。

True Asyncは、必ずしもファイル/ DBアクセスなどのI / O操作にスレッドを使用する必要はありません。これを読んで、I / O操作にスレッドが必要ない理由を理解してください。http://blog.stephencleary.com/2013/11/there-is-no-thread.html

単純な例では、これは純粋なCPUバウンドの計算であるため、Task.Runを使用することで問題ありません。


したがって、Web APIコントローラ内で同期外部APIを使用する必要がある場合、同期呼び出しをTask.Run()でラップしないでください。あなたが言ったように、そうすることは最初のリクエストスレッドをブロックしないままにしますが、それは外部APIを呼び出すために別のプールスレッドを使用しています。実際、この方法を使用すると、理論的には2つのプールスレッドを使用して多くのリクエストを処理できるため、それでも良いアイデアだと思います。
stt106 2017年

私は同意します。Task.Run()内のすべての同期呼び出しを絶対にラップするべきではないと言っているのではありません。私は単に潜在的な問題を指摘しています。
zheng yu 2017

1
@ stt106 I should NOT wrap the synchronous call in Task.Run()正解です。その場合、スレッドを切り替えるだけです。つまり、最初の要求スレッドのブロックを解除していますが、別の要求を処理するために使用できるスレッドプールから別のスレッドを取得しています。唯一の結果は、コールが完全にゼロのゲインで完了した場合のコンテキスト切り替えオーバーヘッドです
Saeb Amini
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.