async-await が追加のスレッドを作成しない場合は、どのようにしてアプリケーションの応答性を高めるのでしょうか? 質問する

async-await が追加のスレッドを作成しない場合は、どのようにしてアプリケーションの応答性を高めるのでしょうか? 質問する

async-を使用すると追加のスレッドは作成されないと何度も言われているのを目にしますawait。これは意味がありません。なぜなら、コンピュータが一度に複数の処理を行っているように見える唯一の方法は、

  • 実際に一度に複数のことを実行する(並列実行、複数のプロセッサの利用)
  • タスクをスケジュールし、それらを切り替えることでシミュレーションします (A を少し、B を少し、A を少しなど)。

ではasyncawaitそのどちらも実行しない場合は、アプリケーションを応答性の高いものにするにはどうすればよいでしょうか。スレッドが 1 つしかない場合、メソッドを呼び出すと、他の操作を実行する前にメソッドが完了するのを待つことになり、そのメソッド内のメソッドは、続行する前に結果を待つ必要があります。

ベストアンサー1

2024 年 3 月 8 日編集:最近、Scott Hanselman と Stephen Toub が、Stephen が async/await をゼロから実装する dotnet ストリームをリリースしました。

このYoutubeビデオ:Stephen Toub 氏と C# でゼロから async/await を書く私が説明するよりもずっとわかりやすく説明してくれます。

強くお勧めします

(元の答えに戻ります)


実際、async/await はそれほど魔法のものではありません。トピック全体は非常に広範囲にわたりますが、あなたの質問に対する迅速かつ十分な回答を得るには、私たちは対処できると思います。

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 秒間フリーズしたように見えます。これは、基本的にループである「メッセージ ポンプ」がブロックされることを意味します。

このループは、Windows に「マウスを動かしたり、何かをクリックしたりした人はいますか? 何かを再描画する必要がありますか? もしそうなら、教えてください!」と継続的に尋ね、その「何か」を処理します。このループは、ユーザーが「button1」をクリックしたというメッセージ (または Windows からの同等の種類のメッセージ) を受け取り、button1_Click上記のメソッドを呼び出しました。このメソッドが戻るまで、このループは待機状態になります。これには 2 秒かかり、その間、メッセージは処理されません。

ウィンドウを扱うほとんどの処理はメッセージを使用して行われます。つまり、メッセージ ループがメッセージの送信を停止すると、たとえ 1 秒でも、ユーザーにはすぐに気付かれます。たとえば、メモ帳または他のプログラムを自分のプログラムの上に移動してから、再び離すと、ウィンドウのどの領域が突然再び表示されるようになったかを示すペイント メッセージがプログラムに大量に送信されます。これらのメッセージを処理するメッセージ ループが何かを待機している場合、つまりブロックされている場合、ペイントは実行されません。

では、最初の例で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キーワードは、巧妙なコンパイラ マジックと組み合わさって、基本的に次のような処理を実行します。「わかりました。ここでボタン クリック イベント ハンドラーから戻ります。(つまり、私たちが待っているもの) が完了したら、まだ実行するコードが残っているのでお知らせください。」

    実際には、SynchronizationContext クラス同期コンテキストに応じて、実行のためにキューに入れられます。Windows フォーム プログラムで使用されるコンテキスト クラスは、メッセージ ループがポンプしているキューを使用して、それをキューに入れます。

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

    ユーザーにとって、UI は再び応答するようになり、他のボタンのクリック、サイズ変更、そして最も重要な再描画が処理されるようになったため、フリーズしているようには見えません。

  5. 2 秒後、待機していた処理が完了し、同期コンテキストによって、メッセージ ループが参照しているキューに「実行すべきコードが他にもあります」というメッセージが配置されます。このコードが、await後のすべてのコードです。

  6. メッセージループがそのメッセージに到達すると、基本的にはそのメソッドを中断したところから「再開」し、awaitメソッドの残りの部分の実行を続行します。このコードはメッセージループから再度呼び出されるため、このコードが適切に使用せずに長い処理を実行するとasync/await、メッセージループが再びブロックされることに注意してください。

ここでは、内部に多くの可動部分があるため、詳細情報へのリンクをいくつか示します。「必要な場合」と言いたかったのですが、このトピックは非常に広範囲にわたるため、可動部分のいくつかを知ることは非常に重要です。async/await は依然として漏れやすい概念であることは、必ず理解することになります。根本的な制限や問題の一部は、依然として周囲のコードに漏れており、漏れない場合は、一見何の理由もなくランダムに壊れるアプリケーションをデバッグしなければならなくなることがよくあります。


では、GetSomethingAsync2 秒で完了するスレッドを起動するとどうなるでしょうか。はい、明らかに新しいスレッドが動作しています。ただし、このスレッドは、このメソッドの非同期性によるものではなく、このメソッドのプログラマが非同期コードを実装するためにスレッドを選択したためです。ほとんどすべての非同期 I/Oはスレッドを使用せず、別のものを使用します。async/await それ自体では新しいスレッドを起動しませんが、明らかに「待機するもの」はスレッドを使用して実装できます。

.NET には、必ずしも独自にスレッドを起動するわけではないが、それでも非同期であるものが多数あります。

  • Web リクエスト (および時間のかかるネットワーク関連のその他の多くのもの)
  • 非同期ファイル読み取りと書き込み
  • 他にも、問題のクラス/インターフェースにSomethingSomethingAsyncまたはおよび という名前のメソッドがありBeginSomethingEndSomethingIAsyncResult関与している場合は、良い兆候です。

通常、これらのものは内部でスレッドを使用しません。


わかりました。それでは、「幅広いトピックのもの」をいくつかお望みですか?

さて、聞いてみましょうRoslynを試すボタンクリックについて:

Roslynを試す

ここでは生成されたクラス全体をリンクするつもりはありませんが、かなりひどい内容です。

おすすめ記事