実際には、非同期/待機はそれほど魔法ではありません。完全なトピックはかなり広いですが、あなたの質問への迅速かつ完全な回答のために、私たちは管理できると思います。
Windowsフォームアプリケーションで簡単なボタンクリックイベントに取り組みましょう。
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
私はそれが何であるかについて明示的に 話さないつもりですGetSomethingAsync
が返ってくるのかについて。これが、たとえば2秒後に完了するものだとしましょう。
従来の非同期ではない世界では、ボタンクリックイベントハンドラは次のようになります。
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
フォームのボタンをクリックすると、このメソッドが完了するのを待つ間、アプリケーションが約2秒間フリーズしているように見えます。発生するのは、「メッセージポンプ」、基本的にはループがブロックされていることです。
このループは継続的にウィンドウに「マウスを動かした、何かをクリックしたなど何かをしたことがありますか?何かを再描画する必要がありますか?そうであれば教えてください!」そしてその「何か」を処理します。このループは、ユーザーが「button1」(またはWindowsからの同等のタイプのメッセージ)をクリックしたというメッセージを受け取り、button1_Click
上記のメソッドを呼び出しました。このメソッドが戻るまで、このループは待機状態のままです。これには2秒かかり、この間、メッセージは処理されていません。
ウィンドウを処理するほとんどのことはメッセージを使用して行われます。つまり、メッセージループがメッセージのポンプを停止すると、たとえ1秒でも、ユーザーはすぐに気づきます。たとえば、メモ帳やその他のプログラムを自分のプログラムの上に移動し、再び離れると、突然突然表示されたウィンドウの領域を示す一連のペイントメッセージがプログラムに送信されます。これらのメッセージを処理するメッセージループが何かを待っている、ブロックされている場合、描画は行われません。
では、最初の例でasync/await
新しいスレッドを作成しない場合、どうやってそれを行うのでしょうか?
まあ、何が起こるかはあなたのメソッドが2つに分割されていることです。これはこれらの幅広いトピックタイプの1つであるため、ここではあまり詳しく説明しませんが、メソッドは次の2つに分割されていると言えます。
- へ
await
の呼び出しを含む、までのすべてのコードGetSomethingAsync
- 次のすべてのコード
await
図:
code... code... code... await X(); ... code... code... code...
再配置:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
基本的に、メソッドは次のように実行されます。
- それまでのすべてを実行します
await
処理を行うGetSomethingAsync
メソッドを呼び出し、2秒後に完了する処理を返します。
これまでのところ、メッセージループから呼び出されたメインスレッドで発生するbutton1_Clickへの元の呼び出しの内部にいます。までのコードにawait
時間がかかる場合でも、UIはフリーズします。この例では、それほど多くありません
何 await
キーワードは、いくつかの巧妙なコンパイラマジックと共に、基本的には「わかりました、わかりました。ここではボタンクリックイベントハンドラから単純に戻ります。あなたが(たとえば、待ってください)完了に取り掛かります。実行するコードがまだ残っているので、知らせてください。」
実際には、SynchronizationContextクラスにそれが完了したことを知らせます。これは、現在実行中の実際の同期コンテキストに応じて、実行のためにキューに入れられます。Windowsフォームプログラムで使用されるコンテキストクラスは、メッセージループがポンプしているキューを使用してそれをキューに入れます。
そのため、メッセージループに戻り、ウィンドウの移動、サイズ変更、他のボタンのクリックなど、メッセージのポンプを自由に続行できるようになりました。
ユーザーにとって、UIは再び応答するようになり、他のボタンクリックの処理、サイズ変更、そして最も重要なのは再描画を行うため、フリーズしていないように見えます。
- 2秒後、私たちが待っているのは完了です。そして、今起きていることは、メッセージループが参照しているキューにメッセージを配置することです。あなたが実行する」と、このコードは待機後のすべてのコードです。
- メッセージループがそのメッセージに到達すると、基本的に、中断したところからそのメソッドを「再入力」し、直後に
await
残りのメソッドの実行を続けます。このコードはメッセージループから再度呼び出されるため、このコードがasync/await
適切に使用せずに長い時間実行すると、メッセージループが再びブロックされます。
ここには多くの可動部分があるので、ここに詳細へのリンクをいくつか示します。「必要な場合」と言いましたが、このトピックは非常に広範であり、それらの可動部分のいくつかを知ることはかなり重要です。常にあなたは非同期/待機がまだ漏れやすい概念であることを理解するでしょう。根本的な制限と問題のいくつかは依然として周囲のコードに漏れており、もしそうでなければ、通常は一見正当な理由がないようにランダムに壊れるアプリケーションをデバッグする必要があります。
ではGetSomethingAsync
、2秒で完了するスレッドを起動するとどうなるでしょうか。はい、明らかに新しいスレッドが出ています。ただし、このスレッドは、このメソッドの非同期性のためではなく、このメソッドのプログラマーが非同期コードを実装するためにスレッドを選択したためです。ほとんどすべての非同期I / O はスレッドを使用せず、異なるものを使用します。async/await
単独では新しいスレッドを起動しませんが、「待機するもの」はスレッドを使用して実装できます。
.NETには、必ずしもそれ自体でスレッドを起動するとは限らないものの、依然として非同期である多くのものがあります。
- Webリクエスト(および時間がかかる他の多くのネットワーク関連のもの)
- 非同期ファイルの読み取りと書き込み
- 問題のクラス/インタフェースは、名前のメソッドがある場合や、より多くの、良い兆候がある
SomethingSomethingAsync
かをBeginSomething
してEndSomething
、そこだIAsyncResult
関与が。
通常、これらのものはフードの下でスレッドを使用しません。
わかりました、それであなたはその「幅広い話題のもの」のいくらかが欲しいですか?
さて、私たちのボタンのクリックについてRoslynを試してみましょう:
Roslynを試す
ここで生成された完全なクラスにリンクするつもりはありませんが、それはかなり悲惨なものです。