C#で非同期メソッドはどのように機能しますか?
-
06-07-2019 - |
質問
プロジェクトの一部で非同期メソッドを使用していますが、アプリケーションのスケーラビリティが大幅に向上するので気に入っています。しかし、非同期メソッドがバックグラウンドで実際にどのように機能するのか疑問に思っていますか? .NET(またはWindows?)は、呼び出しが完了したことをどのように認識しますか?非同期呼び出しの回数に応じて、新しいスレッドが作成されることがわかります(常にではありませんが、…)。なぜですか?
さらに、リクエストが完了するまでにかかる時間を監視したいと思います。概念をテストするために、非同期でWebサービスを呼び出し、ストップウォッチを開始した直後に次のコードを作成しました。
for (int i = 0; i < 10000; i++)
{
myWebService.BeginMyMethod(new MyRequest(), result, new AsyncCallback(callback), i);
stopWatches[i].Start();
}
// Call back stop the stopwatch after calling EndMyMethod
すべてのリクエスト(10000)の開始時間が同じであり、継続時間が線形に増加するため(コール0 =継続時間1、コール1 =継続時間2など)、これは機能しません。非同期メソッドを使用して呼び出しの実際の継続時間を監視するにはどうすればよいですか(要求が実際に実行された瞬間から終了まで)?
更新:非同期メソッドはフローをブロックしますか? .NET ThreadPool
を使用することを理解していますが、 IAsyncResult
が呼び出しが完了したことをどのように認識し、 CallBack
メソッドを呼び出すときですか?
解決
コードは鉄道であり、スレッドは列車です。列車が鉄道に乗ると、コードが実行されます。
BeginMyMethod
はメインスレッドによって実行されます。 BeginMyMethod
の内部を見ると、単に MyMethod
のデリゲートを ThreadPool
のキューに追加します。実際の MyMethod
は、列車プールの列車の1つによって実行されます。 MyMethod
が完了したときに呼び出される完了ルーチンは、残りのコードを実行するメインスレッドではなく、 MyMethod
を実行した同じスレッドによって実行されます。スレッドプールスレッドが MyMethod
の実行でビジーである間、メインスレッドは鉄道システムの他の部分に乗る(他のコードを実行する)か、特定のセマフォが点灯するまで待機するだけです。
したがって、 IAsyncResult
&quot; knowing&quot;のようなものはありません。代わりに、完了ルーチンを呼び出すタイミングは、完了ルーチンは、 MyMethod
の実行が完了した直後にスレッドプールのスレッドによって呼び出される単なるデリゲートです。
やや幼稚な電車のアナロジーを気にしないことを願っています。マルチスレッドを人々に説明する際に何度も助けてくれたことを知っています。
他のヒント
最も重要なのは、 Begin
を呼び出すと、メソッドを実行するためのリクエストがキューイングされることです。メソッドは、実際にはThreadPoolで実行されます。これは、ランタイムによって提供されるワーカースレッドのセットです。
スレッドプールは、非同期タスクがキューに入れられるときに非同期タスクを処理するための固定されたスレッドのセットです。実行時間がより長くかかるのはこのためです-メソッドはそれぞれほぼ同じ時間で実行されますが、キュー内の以前のすべてのメソッドが実行されるまで開始されません。
非同期メソッドを実際に実行するのにかかる時間の長さを監視するには、メソッドの開始時と終了時にタイマーを開始および停止する必要があります。
ThreadPool クラスのドキュメントと記事何が起こっているのかをより適切に説明する非同期メソッドについて。
非同期メソッドは、.NET ThreadPool
を使用して機能します。バックグラウンドで作業するために、作業を ThreadPool
スレッド(必要に応じて作成しますが、通常は単に再利用します)にプッシュします。
あなたの場合、あなたは自分がやっていることを行うことができますが、 ThreadPool
には動作するスレッドの数が限られていることを認識してください。作業をバックグラウンドスレッドに生成します。最初のスレッドはすぐに実行されますが、しばらくするとキューに入れられ、「タスク」まで動作しません。前に完全に実行します。これにより、スレッドの外観がますます長くなります。
ただし、ストップウォッチの基準には多少の欠陥があります。 1つのタスクを完了するのにN回ではなく、N個のタスクを完了するのにかかる合計時間を測定する必要があります。これははるかに便利な指標になります。
BeginMyMethod()
の前に実行時間の大部分が発生する可能性があります。その場合、測定値が低すぎます。実際、APIによっては、 BeginMyMethod()
がスタック自体を離れる前にコールバックを呼び出す場合があります。この場合、 StopWatch.Start()
の呼び出しを上に移動すると役立ちます。