Task.Runを正しく使用する場合と、async-awaitのみを使用する場合の質問

Task.Runを正しく使用する場合と、async-awaitのみを使用する場合の質問

適切なアーキテクチャをいつ使用するかについてご意見をお聞かせください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. 、それとも、他の場所で再利用できるように、内部で実行されている CPU バウンド メソッドのみをラップするべきでしょうかTask.Run? ここでは、コアの奥深くにあるバックグラウンド スレッドで作業を開始するのが良いアイデアかどうかはわかりません。

広告(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

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

  • 一度に 50 ミリ秒以上 UI スレッドをブロックしないでください。
  • UI スレッドでは 1 秒あたり約 100 回の継続をスケジュールできますが、1000 回は多すぎます。

使用すべきテクニックは 2 つあります。

1)ConfigureAwait(false)できるときに使用してください。

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

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

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

2) Task.RunCPU バウンド メソッドを呼び出すために使用します。

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

したがって、純粋に CPU に依存する作業は次のようになります。

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

これは次のように呼び出しますTask.Run:

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

CPU バウンドと I/O バウンドが混在するメソッドには、AsyncCPU バウンドの性質を示すドキュメントを含むシグネチャが必要です。

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

これは、次のようにも呼び出すことができますTask.Run(部分的に CPU に依存するため)。

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

おすすめ記事