1. ホーム
  2. c#

[解決済み] Task.Runの正しい使い方とasync-awaitだけの使い方

2022-03-14 21:44:59

質問

を使用する際の正しいアーキテクチャについてご意見を伺いたいと思います。 Task.Run . WPF .NET 4.5でUIにラグが発生しています。 アプリケーション(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
}

解決方法は?

注意 UIスレッドで作業を行うためのガイドライン 私のブログで紹介しています。

  • UIスレッドを一度に50ミリ秒以上ブロックしない。
  • UIスレッドでの継続は1秒間に100回までとし、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());