1. ホーム
  2. c#

[解決済み】async-awaitが追加のスレッドを作成しないのであれば、どのようにしてアプリケーションの応答性を高めているのでしょうか?

2022-03-28 05:11:33

質問

を使用することは、何度も何度も言われています。 async - await は、追加のスレッドを作成しません。コンピュータが一度に1つ以上のことを行っているように見える唯一の方法は、次のとおりです。

  • 実際に1つ以上のことを同時に行っている(複数のプロセッサを利用した並列実行を行っている)。
  • タスクのスケジューリングとその切り替えによるシミュレーション(Aを少し、Bを少し、Aを少し、など)

では、もし async - await はそのどちらも行わないのであれば、どうやってアプリケーションを応答性の高いものにできるでしょうか?もしスレッドが1つしかないのであれば、どんなメソッドを呼んでも、何かする前にそのメソッドの完了を待つことになり、そのメソッドの中のメソッドは結果を待ってから処理しなければならない、というように。

解決方法は?

実は、async/awaitはそれほど魔法のようなものではないのです。このトピックはかなり広範囲にわたりますが、あなたの質問に対する迅速かつ十分な回答は、私たちが管理できると思います。

Windows Forms アプリケーションの簡単なボタンクリックイベントに取り組んでみましょう。

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秒程度フリーズしているように見えます。これは、メッセージポンプがブロックされているためです。

このループは、ウィンドウズに対して継続的に "マウスを動かしたり、何かをクリックするなど、誰かが何かをしたのか?何かを塗り直す必要がありますか?もしそうなら、教えてください!" と、その "something" を処理しています。このループは、ユーザーが "button1" をクリックしたというメッセージ (または Windows からの同等のタイプのメッセージ) を受け取り、最終的に私たちの button1_Click というメソッドがあります。このメソッドが返ってくるまで、このループは待ちぼうけを食らうことになります。このループには2秒かかり、その間は何のメッセージも処理されません。

ウィンドウズを扱うほとんどのことはメッセージを使って行われます。つまり、メッセージループがほんの一瞬でもメッセージを送るのを止めると、ユーザーにはすぐに気づかれてしまうのです。例えば、メモ帳や他のプログラムを自分のプログラムの上に移動させ、また離れたら、今度は突然ウィンドウのどの領域が再び見えるようになったかを示すペイントメッセージが次々と自分のプログラムに送られるのです。これらのメッセージを処理するメッセージループが何かを待っていたり、ブロックされていたりすると、ペイントは行われません。

そこで、最初の例で言えば async/await は新しいスレッドを作成しませんが、どのようにそれを行うのでしょうか?

さて、何が起こるかというと、あなたのメソッドが2つに分かれるのです。これは大まかなトピックの1つなので、あまり詳しくは説明しませんが、メソッドがこの2つに分割されていることだけは確かです。

  1. に至るまでのすべてのコードは await への呼び出しを含む GetSomethingAsync
  2. に続くすべてのコード await

イラストはこちら

code... code... code... await X(); ... code... code... code...

再整理した。

code... code... code... var x = X(); await X; code... code... code...
^                                  ^          ^                     ^
+---- portion 1 -------------------+          +---- portion 2 ------+

基本的にはこのようなメソッドが実行されます。

  1. までを実行します。 await
  2. を呼び出しています。 GetSomethingAsync メソッドを実行し、その結果を返します。 2秒後の未来に完成するもの

    ここまでは、まだbutton1_Clickの最初の呼び出しの内部で、メッセージループから呼び出されたメインスレッドで起こっています。もし await に時間がかかると、やはりUIがフリーズしてしまいます。この例では、それほどでもありません。

  3. 何が await キーワードは、巧妙なコンパイラ・マジックと一緒に、基本的に次のようなことをします:OK, あのね、私はここで単にボタンのクリック・イベント・ハンドラから戻るつもりです。あなた(つまり、私たちが待っているもの)が完了する頃になったら、私に知らせてください、なぜならまだ実行すべきコードが残っているからです".

    実際には SynchronizationContext クラス が完了したことを知らせると、今実行している実際の同期コンテキストに応じて、実行のためのキューが作成されます。Windows Forms プログラムで使用されるコンテキストクラスは、メッセージループが汲み上げているキューを使用してキューイングします。

  4. そのため、メッセージループに戻り、ウィンドウの移動、サイズ変更、他のボタンのクリックなど、自由にメッセージを送り続けることができるようになりました。

    ユーザーにとっては、UI が再びレスポンシブになり、他のボタンのクリックやサイズ変更、そして最も重要なことを処理できるようになりました。 再描画 そのため、フリーズすることはないようです。

  5. 2秒後、私たちが待っているものが完了し、今起こっていることは、それ(まあ、同期コンテキスト)がメッセージループが見ているキューにメッセージを入れ、"ヘイ、あなたが実行するためのいくつかのコードを得た"と言い、このコードがすべてのコードであることです。 を実行します。
  6. メッセージループがそのメッセージに到達すると、基本的にそのメソッドに再入力します。 await で、そのメソッドの残りを実行し続ける。このコードは再びメッセージループから呼び出されるため、もしこのコードが async/await が適切に動作しないと、再びメッセージループがブロックされます。

ここでは、ボンネットの下に多くの可動部品があるため、より多くの情報へのリンクをいくつか紹介します。 を知ることは、かなり重要です。 それらの可動部分の一部 . async/awaitがまだリーキーコンセプトであることは必ず理解できるはずです。そして、もしそうでなければ、通常、何の理由もなく不規則に壊れるアプリケーションをデバッグしなければならない羽目になります。


OK、では、次の場合はどうでしょう。 GetSomethingAsync は2秒後に完了するスレッドを紡ぎ出しますか?はい、その場合、明らかに新しいスレッドが存在しています。しかし、このスレッドは なぜなら このメソッドのプログラマが非同期コードを実装するためにスレッドを選択したからです。ほとんどすべての非同期I/O はしない。 はスレッドを使用し、別のものを使用します。 async/await 自分自身で は新しいスレッドを立ち上げませんが、明らかにquot; things we wait for"はスレッドを使用して実装されるかもしれません。

.NETには、必ずしも単独でスレッドをスピンアップしないものの、非同期であるものが多くあります。

  • Webリクエスト(その他ネットワーク関連で時間がかかるものが多い)
  • 非同期ファイル読み出し/書き込み
  • という名前のメソッドを持つクラス/インターフェースは、良いサインです。 SomethingSomethingAsync または BeginSomethingEndSomething があり、そこに IAsyncResult を巻き込んだ。

通常、このようなものは、下手にスレッドを使用しない。


OK、では、そのquot;広い話題のもの"が欲しいのですね?

では、お願いします。 Roslynを試す ボタンクリックについて

Roslynを試す

生成されたクラスの全容はここではリンクしませんが、かなりグロい内容です。