web-dev-qa-db-ja.com

CPUバインドタスクにC#async / awaitを使用する理由

C#でasync/awaitキーワードのコツと、非同期プログラミングを容易にする方法を理解しています。db呼び出しなどのI/Oバインドタスクが実行されている間、スレッドを他の場所で使用できます。

Async/awaitがI/Oバウンドタスク用であり、CPUバウンドタスク用ではないことを何度も読みました。 CPUバウンドタスクは、別のバックグラウンドスレッドで実行する必要があります。これらの中で数回言及 ビデオ 。大丈夫。

ただし、Task.Runを使用して新しいスレッドで長時間実行されるCPUにバインドされた作業を開始する場合、ある時点でawaitする必要があります。では、CPUにバインドされたタスクでも、ここでasync/awaitを使用していませんか?以下の例を参照してください。

public async Task SomeMethodAsync()
{
    int result = await Task.Run(() =>
    {
        // Do lots of CPU bound calculations...

        return result;
    }

    // Then do something with the result.
}
9
Tophat Gordon

async/awaitは、ジョブがI/Oバウンドであるかどうかに関係なく、asyncプログラミングを行うための現代的で最も簡単な方法です。 Taskにスレッドが必要かどうか。

たとえば、メインスレッドは、子タスクでawaitを使用して、ミスピギーが1年間に昼食をとった回数を計算できるので、WinFormsやWPFに最適です(かなり長くて複雑なCPUバインド操作としましょう)。完了したら、すぐ下の次の行を実行します。これにより、従来の非同期コールバックとは異なり、コードが非常に直感的になります。 WaitOnesまたはその他のメカニズムとそれに伴うジャグリング行為。

MSDN:

非同期プログラミングを使用することで、パフォーマンスのボトルネックを回避し、アプリケーションの全体的な応答性を向上させることができます。ただし、非同期アプリケーションを作成するための従来の手法複雑であり、書き込み、デバッグ、保守が難しい。

Visual Studio 2012は、.NET Framework 4.5とWindowsランタイムの非同期サポートを活用する、簡素化されたアプローチである非同期プログラミングを導入しています。 コンパイラーは、開発者が使用していた困難な作業、およびアプリケーションが同期に似た論理構造を保持しますコードその他...

OP:

Async/awaitがI/Oバウンドタスク用であることを何度も読みました

不正解です。 async/awaitは、コンパイラーがより多くの作業を行う非同期プログラミングの省略形です。これは、I/Oに限定されたタスクだけのものではありません。 CPUバウンドタスクは通常、スレッドプールスレッドを使用します。

CPUバウンドタスクは、別のバックグラウンドスレッドで実行する必要があります。

はい...しかし、それはあなたがawaitTaskすることができないという意味ではありません。 I/Oバウンドタスクとは異なり、CPUバウンドタスクは操作するためにスレッドが必要で、定義によりワーカースレッドなので、使用可能なスレッドプールから1つ取得します。

ただし、Task.Runを使用して新しいスレッドで長時間実行されるCPUにバインドされた作業を開始するときは、ある時点でそれを待つ必要があります

await a Taskする必要はありません。そのようなタスクは「ファイアアンドフォーゲット」と呼ばれます。 awaitも実際には起動せず、タスクは「ホット」です。ただし、待機しないと、アプリケーションの終了時にタスクが完了しないリスクがあります。例えばコンソールアプリがTaskを起動し、それを待たずに終了します。

では、CPUにバインドされたタスクでも、ここでasync/awaitを使用していませんか?

そうです、それはI/Oにバインドされているかどうかに関係なく、あらゆるTaskに使用できます。

もっと

13
MickyD

何かのためにawaitした場合、その待たされた操作から結果を得るという約束があります。通常、待機中の操作は、将来実行される非同期\バックグラウンド操作であると想定しています。その間、その操作の結果とは無関係に実行できる作業を続行できます。

async/awaitは、それをアーカイブするのがコンパイラの助けであることを正確に行います。 awaitは、コントロールを呼び出し元に返し、待機中の操作を非同期で処理します。その非同期性は、さまざまな方法で提供できます。

I/O操作はカーネル空間に入ると非同期で処理されるため、I/O操作用に追加のスレッドを作成する必要はありません。

システムAPI呼び出しの後、要求はカーネル空間になり、OSのネットワークサブシステム(Linuxカーネルの/ netなど)に到達します。ここでは、OSはネットワーク要求を非同期で処理します。詳細は、使用するOSによって異なります(デバイスドライバーの呼び出しは、ランタイムに送り返される信号としてスケジュールされるか、デバイスドライバーの呼び出しが行われてから信号が送り返される場合があります)。ただし、最終的にはランタイムに通知されます。ネットワーク要求が進行中です。この時点で、デバイスドライバーの作業は、スケジュールされているか、進行中か、または既に完了しています(要求は既に "ネットワーク経由"で送信されています)-これはすべて非同期で行われているため、デバイスドライバーはすぐに他のものを処理してください!

一方、CPUオペレーションの場合、何らかの方法でCPUに非同期で処理を行うように指示する必要があり、通常、これは別のスレッドから実行されます(Task.Run)。

CPUバインド非同期コードは、I/Oバインド非同期コードとは少し異なります。作業はCPUで行われるため、スレッドを計算専用にする方法はありません。 asyncとawaitを使用すると、バックグラウンドスレッドとやり取りして、asyncメソッドの呼び出し元の応答性を保つためのクリーンな方法が提供されます。これは共有データを保護しないことに注意してください。共有データを使用している場合でも、適切な同期戦略を適用する必要があります。

重要なのは、どちらの場合でも、約束が満たされるのを待つためにawaitを実行する必要があるということです。

1
Johnny