web-dev-qa-db-ja.com

asp.netコアとケストレルスレッドプールでの非同期処理

私はASP.NETCoreとC#に不慣れで、一般的にJavaの世界から来ており、async/awaitキーワードがどのように機能するかについて少し混乱しています: この記事 メソッドが非同期としてマークされている場合、それは次のことを意味することを説明します

「このメソッドには、非同期操作の待機を伴う制御フローが含まれているため、非同期操作がこのメソッドを適切な場所で再開できるように、コンパイラーによって継続渡しスタイルに書き換えられます。」 非同期メソッドの要点は、可能な限り現在のスレッドにとどまるということです

だから私はそれが非同期メソッドとバイトコード/仮想に実装された同じスレッドで実行されているその呼び出し元の間で制御を渡す非常にクールな方法だと思いましたマシンレベル。私は以下のコードを期待したことを意味します

_public static void Main(string[] args)
{
    int delay = 10;
    var task = doSthAsync(delay);
    System.Console.WriteLine("main 1: " + Thread.CurrentThread.ManagedThreadId);
    // some synchronous processing longer than delay:
    Thread.Sleep(2 * delay)
    System.Console.WriteLine("main 2: " + Thread.CurrentThread.ManagedThreadId);
    task.Wait();
}

public static async Task<String> doSthAsync(int delay)
{
    System.Console.WriteLine("async 1: " + Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(delay);
    System.Console.WriteLine("async 2: " + Thread.CurrentThread.ManagedThreadId);
    return "done";
}
_

書くだろう

非同期1:1 
メイン1:1 
メイン2:1 
非同期2:1 

doSthAsyncキーワードを使用してMainからawaitに制御を渡し、次にtask.Wait()を使用してdoSthAsyncに戻します(これはすべてシングルスレッド)。

ただし、上記のコードは実際には出力されます

非同期1:1 
メイン1:1 
非同期2:4 
メイン2:1 

これは、現在のスレッドがビジーである場合に、「非同期」メソッドを別のスレッドに委任する方法にすぎないことを意味します。これは、ほぼ正確に反対です。言及された記事が述べていること:

メソッドの「async」修飾子は、「このメソッドがワーカースレッドで非同期に実行されるように自動的にスケジュールされる」という意味ではありません。

したがって、非同期メソッドの「継続」は明らかにであるか、少なくともである可能性があります。 )別のスレッドで実行するようにスケジュールされていますが、C#アプリのスケーラビリティの基本と思われる質問がいくつかあります。

各非同期メソッドは新しく生成されたスレッドで実行できますか、それともC#ランタイムはこの目的のために特別なスレッドプールを維持していますか?後者の場合、このプールのサイズを制御するにはどうすればよいですか? UPDATE:@ Servyのおかげで、CMDラインアプリの場合はスレッドプールスレッドになることがわかりましたが、それでもわかりません。これらのスレッドプールスレッドの数を制御する方法を知っています)。

ASP.NET Coreの場合はどうでしょうか。Kestrelが非同期エンティティフレームワーク操作またはその他の(ネットワーク)I/Oを実行するために使用するスレッドプールのサイズを制御するにはどうすればよいですか? HTTPリクエストを受け入れるために使用するのと同じスレッドプールですか?
UPDATE:ありがとう this and this article by Stephen Cleary I nowデフォルトでは「kind-of-single-threadly」で実行されることを理解してください。つまり、特定のHTTPリクエストのSynchronizationContext内に常に1つのスレッドが存在しますが、このスレッドは、非同期の場合に別のスレッドに切り替えられる可能性があります。操作は終了としてマークされ、タスクが再開されます。また、ConfigureAwait(false)を使用して、「指定されたSynchronizationContextから」任意の非同期メソッドをスレッドプールスレッドにディスパッチできることも学びました。それでも、スレッドプールスレッドの数を制御する方法と、それがKestrelがHTTPリクエストを受け入れるために使用するのと同じプールであるかどうかはわかりません。

すべてのI/Oが非同期で適切に実行されるかどうかを理解しているので、これらのプールのサイズ(実際には1つのスレッドプールであるか2つであるか)は、外部リソースに対して開かれている発信接続の数とは関係ありません。 DBのようにね? (非同期コードを実行しているスレッドは、新しいHTTP要求を継続的に受け入れ、その処理の結果として、ブロッキングなしで可能な限り高速に発信接続(DBまたはWebサーバーへのWebサーバーへの)を開きます)
これは、DBを強制終了したくない場合は、接続プールの適切な制限を設定し、使用可能な接続の待機も非同期で行われるようにする必要があることを意味します。
私が正しいと仮定すると、長時間続くI/O操作が誤って同期的に実行された場合に備えて、スレッドプールスレッドの数を制限して、多数のスレッドが次のように生成されるのを防ぐことができます。この同期操作で多数のスレッドがブロックされた結果(つまり、このようなバグのために異常な数のスレッドを生成するよりも、アプリのパフォーマンスを制限する方が良いと思います)

また、「好奇心から」という質問もあります。同じスレッドで非同期タスクを実行するために、実際にはasync/awaitが実装されていないのはなぜですか。 Windowsで実装する方法はわかりませんが、Unixでは、select/pollシステムコールまたはシグナルのいずれかを使用して実装できるため、Windowsでもこれを実現する方法があり、非常に優れた言語になると思います。確かに機能。
UPDATE:正しく理解していれば、これはほぼ SynchronizationContextがnullではありません。つまり、コードは「kind-of-single-threadly」で実行されます。任意の時点で、特定のSynchronizationContext内に正確に1つのスレッドが存在します。しかし、それが実装される方法は、非同期タスクがデッドロックするのを待つ同期メソッドを作りますよね? (ランタイムは、このタスクが終了するのを待っているのと同じスレッドで実行するように、タスクのマークを終了としてスケジュールします)

ありがとう!

9
morgwai

デフォルトでは、SynchronizationContextがない場合、または待機中のタスクでConfigureAwait(false)が呼び出された場合、その継続は、 ThreadPoolから静的メソッドを使用してアクセスできるCLRが維持するスレッドプールで実行されますクラス
そのサイズを制御するためのThreadPoolからの.netコアメソッド( setMaxThreads および setMinThreads )はまだ実装されていません:
https://github.com/dotnet/cli/issues/889
https://github.com/dotnet/corefx/issues/592
ただし、ここで説明するように、project.jsonファイルで静的にサイズを設定することは可能です。
https://github.com/dotnet/cli/blob/rel/1.0.0/Documentation/specs/runtime-configuration-file.md#runtimeoptions-section-runtimeconfigjson

kestrelには、libuvを使用したリクエストの非同期処理用の独自のプールがあります。これは、ThreadCountクラスの static KestrelServerOptions property によって制御されます。

関連記事:
http://blog.stephencleary.com/2012/02/async-and-await.html
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
https://blogs.msdn.Microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
https://msdn.Microsoft.com/en-us/magazine/gg598924.aspx

私がそれを見つけることを可能にしたヒントについては、@ Servyと@Damien_The_Unbelieverに感謝します

5
morgwai

awaitは、SynchronizationContext.Currentの値を使用して継続をスケジュールします。コンソールアプリでは、デフォルトで現在の同期コンテキストはありません。したがって、スレッドプールスレッドを使用して継続をスケジュールします。 winforms、WPF、winphoneアプリケーションなど、メッセージループのあるアプリケーションでは、メッセージループは、現在の同期コンテキストを、すべてのメッセージをメッセージループに送信するものに設定し、UIスレッドで実行します。

ASPアプリケーションでは、現在の同期コンテキストもありますが、特定のスレッドではありません。むしろ、同期コンテキストの次のメッセージを実行するときは、スレッドプールスレッドを使用します。適切なリクエストのリクエストデータを取得してから、メッセージを実行します。これは、ASPアプリケーションで同期コンテキストを使用する場合、複数の操作が存在しないことがわかっていることを意味します。一度に実行されますが、必ずしもすべての応答を処理する単一のスレッドであるとは限りません。

10
Servy

ここで言及する価値があるのは、質問がdot net Coreに関するものであるため、。NetそこにはありませんSynchronizationContext indot net core

非同期タスクは、スレッドプールの任意のスレッドを使用して実行されます。

ConfigureAwaitは、.Netで使用されると思わない限り、関係ありません。

https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html を参照してください

および ASP.NET Coreに関連するConfigureAwait(false)?

0
AmitE