web-dev-qa-db-ja.com

ASP.NET Coreの同期アクションと非同期コントローラーアクションの間に大きな違いはありません

ASP.NETコアのsyncasyncコントローラーアクションの違いをテストするために、コントローラーでいくつかのアクションメソッドを作成しました。

[Route("api/syncvasync")]
public class SyncVAsyncController : Controller
{
    [HttpGet("sync")]
    public IActionResult SyncGet()
    {
        Task.Delay(200).Wait();

        return Ok(new { });
    }

    [HttpGet("async")]
    public async Task<IActionResult> AsyncGet()
    {
        await Task.Delay(200);

        return Ok(new { });
    }
}

次に、同期エンドポイントの負荷テストを行いました: enter image description here

...非同期エンドポイントが続きます: enter image description here

遅延を1000ミリ秒に増やした場合の結果は次のとおりです enter image description hereenter image description here

ご覧のとおり、毎秒リクエストに大きな違いはありません-非同期エンドポイントが毎秒より多くのリクエストを処理することを期待しました。何か不足していますか?

22
Carl Rippon

はい、非同期は速度に関するものではなく、1秒あたりの要求の概念にわずかに関連しているという事実を見逃しています。

非同期は、1つのことだけを行います。タスクが待機されており、そのタスクがCPUにバインドされた作業を含まない場合、その結果、スレッドはアイドル状態になり、そのスレッド潜在的にプールに戻って他の作業を行うために解放できます。

それでおしまい。簡単に言えば非同期です。非同期のポイントは、リソースをより効率的に利用することです。スレッドがつながれていて、つま先をタップしてI/O操作が完了するのを待っている状況では、代わりに他の作業を行うことができます。これにより、内部化すべき2つの非常に重要なアイデアが得られます。

  1. 非同期!=高速。実際、非同期はslowerです。非同期操作にはオーバーヘッドが伴います。コンテキストの切り替え、ヒープのオンとオフのシャッフルされるデータなどです。これにより、処理時間が長くなります。場合によってはマイクロ秒しか話していない場合でも、非同期はalwaysが同等の同期プロセスよりも遅くなります。期間。完全停止。

  2. Asyncは、サーバーに負荷がかかっている場合にのみ、何でも購入します。サーバーにストレスがかかっている場合にのみ、asyncが必要なブリージングルームを提供しますが、syncはサーバーに負担をかけます。すべてはスケールです。サーバーがごくわずかな量のリクエストのみを処理している場合、同期を介した違いは見られない可能性が高く、前述したように、more皮肉なことに、関連するオーバーヘッドのためのリソース。

それは、非同期を使用すべきではないという意味ではありません。あなたのアプリが今日人気がないとしても、それが後になることを意味するものではなく、非同期をサポートするためにその時点ですべてのコードを再調整するのは悪夢です。非同期のパフォーマンスコストは通常​​、ごくわずかであり、必要になった場合、命の恩人になります。

[〜#〜] update [〜#〜]

非同期のパフォーマンスコストを無視できるようにするために、C#での非同期のほとんどの議論では、明確ではない、または実際に詳しく説明されているいくつかの役立つヒントがあります。

  • できる限りConfigureAwait(false)を使用してください。

    _await DoSomethingAsync().ConfigureAwait(false);
    _

    いくつかの特定の例外を除いて、ほとんどすべての非同期メソッド呼び出しの後にこれを行う必要があります。 ConfigureAwait(false)は、非同期操作中に同期コンテキストを保存する必要がないことをランタイムに伝えます。デフォルトでは、非同期操作を待つと、スレッドスイッチ間でスレッドローカルを保持するオブジェクトが作成されます。これは、非同期操作の処理に伴う処理時間の大部分を占め、多くの場合、完全に不要です。本当に重要な場所は、アクションメソッド、UIスレッドなど、保存する必要のあるスレッドに関連する情報がある場所だけです。このコンテキストを保持する必要があるのは1回だけです。たとえば、アクションメソッドが、同期コンテキストがそのままの状態で非同期操作を待機している限り、その操作自体は同期コンテキストが保持されない他の非同期操作を実行できます。このため、アクションメソッドなどでawaitの使用を最小限に抑え、代わりにそのアクションメソッドが呼び出すことができる単一の非同期メソッドに複数の非同期操作をグループ化するようにしてください。これにより、非同期の使用に伴うオーバーヘッドが削減されます。これは、ASP.NET MVCのアクションの問題にすぎないことに注意してください。 ASP.NET Coreは、静的ではなく依存性注入モデルを利用しているため、心配するスレッドローカルはありません。他では、ASP.NET CoreアクションでConfigureAwait(false)を使用できますが(ASP.NET MVCでは使用できません)can。実際、試してみると、実行時エラーが発生します。

  • 可能な限り、保存する必要のある地元の人の数を減らす必要があります。 awaitを呼び出す前に初期化した変数はヒープに追加され、タスクが完了するとポップされます。宣言すればするほど、ヒープに追加されます。特に、大きなオブジェクトグラフは、ここで大混乱を招く可能性があります。これは、大量の情報がヒープを行き来するためです。これは避けられないこともありますが、注意する必要があります。

  • 可能であれば、async/awaitキーワードを削除します。たとえば、次のことを考慮してください。

    _public async Task DoSomethingAsync()
    {
        await DoSomethingElseAsync();
    }
    _

    ここで、DoSomethingElseAsyncは、待機およびアンラップされたTaskを返します。次に、Taskから戻るための新しいDoSometingAsyncが作成されます。ただし、代わりに、メソッドを次のように記述した場合:

    _public Task DoSomethingAsync()
    {
        return DoSomethingElseAsync();
    }
    _

    Taskによって返されるDoSomethingElseAsyncは、DoSomethingAsyncによって直接返されます。これにより、大幅なオーバーヘッドが削減されます。

57
Chris Pratt

asyncはパフォーマンスよりもスケーリングに関することを忘れないでください。上記のパフォーマンステストに基づいて、アプリケーションのスケーリング能力が向上することはありません。スケーリングを適切にテストするには、ideallyがprod環境と一致する適切な環境で負荷テストを行う必要があります。

非同期のみに基づいてパフォーマンスの改善をマイクロベンチマークしようとしています。 (コード/アプリケーションによって異なりますが)パフォーマンスが明らかに低下することがあります。これは、非同期コードにいくらかのオーバーヘッドがあるためです(コンテキストスイッチング、ステートマシンなど)。そうは言っても、99%の時間、コードを記述してスケーリングする必要があります(これもアプリケーションによって異なります)。この場合、いわば木々の森は見えません。 asyncができることをテストするときは、マイクロベンチマークではなく負荷テストに実際に注意する必要があります

0
Dave Black