Task.Runを正しく使用し、async-awaitを使用する場合


317

使用する際の正しいアーキテクチャーについて、ご意見をお聞かせくださいTask.Run。WPF .NET 4.5アプリケーション(Caliburn Microフレームワークを使用)でUIが遅くなっています。

基本的に私はやっています(非常に単純化されたコードスニペット):

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // Makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync();

      HideLoadingAnimation();
   }
}

public class ContentLoader
{
    public async Task LoadContentAsync()
    {
        await DoCpuBoundWorkAsync();
        await DoIoBoundWorkAsync();
        await DoCpuBoundWorkAsync();

        // I am not really sure what all I can consider as CPU bound as slowing down the UI
        await DoSomeOtherWorkAsync();
    }
}

私が読んだり見たりした記事/ビデオから、それawait asyncが必ずしもバックグラウンドスレッドで実行されているわけではなく、バックグラウンドで作業を開始するには、awaitでラップする必要がありますTask.Run(async () => ... )。を使用async awaitしてもUIはブロックされませんが、UIスレッドで実行されているため、遅延が発生します。

Task.Runを配置するのに最適な場所はどこですか?

私はちょうどいいですか

  1. これは.NETのスレッド処理が少ないため、外部呼び出しをラップします。

  2. 、またはTask.Run他の場所で再利用できるようにするため、内部で実行されているCPUバインドメソッドのみをラップする必要がありますか?コアの深いバックグラウンドスレッドで作業を開始することが良いアイデアかどうか、ここではわかりません。

広告(1)、最初のソリューションは次のようになります:

public async void Handle(SomeMessage message)
{
    ShowLoadingAnimation();
    await Task.Run(async () => await this.contentLoader.LoadContentAsync());
    HideLoadingAnimation();
}

// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.

広告(2)、2番目のソリューションは次のようになります。

public async Task DoCpuBoundWorkAsync()
{
    await Task.Run(() => {
        // Do lot of work here
    });
}

public async Task DoSomeOtherWorkAsync(
{
    // I am not sure how to handle this methods -
    // probably need to test one by one, if it is slowing down UI
}

ところで、(1)の行await Task.Run(async () => await this.contentLoader.LoadContentAsync());は単にである必要がありますawait Task.Run( () => this.contentLoader.LoadContentAsync() );。私の知る限り、あなたは秒awaitasync内側を追加することによって何も得ませんTask.Run。また、パラメータを渡していないので、これによりがさらに単純化されawait Task.Run( this.contentLoader.LoadContentAsync );ます。
ToolmakerSteve 2018年

内部で2番目の待機がある場合、実際には少し違いがあります。この記事を参照してください。私はそれが非常に便利であることに気付きましたが、この特定の点だけでは同意せず、待機するのではなく直接タスクに戻ることを好みます。(コメントで提案した
Lukas K

回答:


365

私のブログで収集されたUIスレッド作業を実行するためガイドラインに注意してください

  • 一度に50ミリ秒を超えてUIスレッドをブロックしないでください。
  • 1秒あたり最大100個の継続をUIスレッドでスケジュールできます。1000は多すぎます。

使用する必要がある2つの手法があります。

1)ConfigureAwait(false)可能な場合に使用します。

たとえば、のawait MyAsync().ConfigureAwait(false);代わりにawait MyAsync();

ConfigureAwait(false) 伝えます await現在のコンテキストで再開する必要がないことをます(この場合、「現在のコンテキストで」とは「UIスレッドで」を意味します)。ただし、そのasyncメソッドの残りの部分(の後ConfigureAwait)では、現在のコンテキスト(たとえば、UI要素の更新)にいると想定することはできません。

詳細については、MSDNの記事を参照してください。 非同期プログラミングのベストプラクティス

2)使用 Task.Run CPUにバインドされたメソッドを呼び出すためにします。

を使用する必要Task.Runがありますが、再利用可能にするコード(ライブラリコードなど)内では使用しないでください。あなたは使うようTask.Run呼び出し、メソッドの実装の一部としてではなく、メソッドの。

したがって、純粋にCPUにバインドされた作業は次のようになります。

// Documentation: This method is CPU-bound.
void DoWork();

どちらを使用するか Task.Run

await Task.Run(() => DoWork());

CPUバウンドとI / Oバウンドが混在するメソッドには、Async CPUバウンドの性質を指摘するドキュメント署名がです。

// Documentation: This method is CPU-bound.
Task DoWorkAsync();

どちらを使用して呼び出すかTask.Run(部分的にCPUにバインドされているため):

await Task.Run(() => DoWorkAsync());

4
あなたの速い応答をありがとう!あなたが投稿したリンクを知っていて、ブログで参照されているビデオを見ました。実際、私がこの質問を投稿したのはそのためです。ビデオで(応答と同じように)言われていますが、コアコードでTask.Runを使用しないでください。しかし、私の問題は、応答を遅くしないように、使用するたびにそのようなメソッドをラップする必要があることです(私のコードはすべて非同期でブロックされていないことに注意してください。CPUにバインドされたメソッド(多くのTask.Run呼び出し)をラップするだけの方が良いのか、それともすべてを1つのTask.Runで完全にラップする方が良いのかについても混乱しています。
Lukas K

12
すべてのライブラリメソッドでを使用する必要がありますConfigureAwait(false)。それを最初に行うと、それTask.Runが完全に不要になる場合があります。あなたがいる場合、まだ必要Task.Run、それは、あなたが一度または何度もそれを呼び出すかどうか、この場合には、実行時にあまり違いはありませんので、ちょうどあなたのコードのための最も自然なことです何をすべきか。
Stephen Cleary 2013

2
最初のテクニックが彼をどのように助けるかわかりません。ConfigureAwait(false)cpu-boundメソッドで使用する場合でも、cpu-boundメソッドを実行するのは依然としてUIスレッドであり、その後のすべてがTPスレッドで実行される可能性があります。または私は何かを誤解しましたか?
Darius

4
@ user4205580:いいえ、Task.Run非同期署名を理解するため、完了するまでDoWorkAsync完了しません。余分なasync/ awaitは不要です。エチケットに関するブログシリーズTask.Runで、「なぜ」の詳細を説明します。
Stephen Cleary 2015年

3
@ user4205580:いいえ。「コア」非同期メソッドの大部分は内部で使用していません。正常な「コア」非同期メソッドを実装する方法は使用することであるTaskCompletionSource<T>または例えばその速記表記の一つFromAsync非同期メソッドがスレッドを必要としない理由を詳しく説明しブログ投稿があります
Stephen Cleary 2015年

12

ContentLoaderの1つの問題は、内部的に順次動作することです。より良いパターンは、作業を並列化し、最後に同期することです。

public class PageViewModel : IHandle<SomeMessage>
{
   ...

   public async void Handle(SomeMessage message)
   {
      ShowLoadingAnimation();

      // makes UI very laggy, but still not dead
      await this.contentLoader.LoadContentAsync(); 

      HideLoadingAnimation();   
   }
}

public class ContentLoader 
{
    public async Task LoadContentAsync()
    {
        var tasks = new List<Task>();
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoIoBoundWorkAsync());
        tasks.Add(DoCpuBoundWorkAsync());
        tasks.Add(DoSomeOtherWorkAsync());

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }
}

明らかに、他の以前のタスクからのデータが必要なタスクがある場合、これは機能しませんが、ほとんどのシナリオで全体的なスループットが向上します。

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