async-awaitが追加のスレッドを作成しない場合、どのようにしてアプリケーションを応答可能にしますか?


241

何度も繰り返しますが、async- を使用awaitしても追加のスレッドは作成されません。コンピュータが一度に複数のことを実行しているように見える唯一の方法は、

  • 実際に一度に複数のことを実行している(並列実行、複数のプロセッサの使用)
  • タスクのスケジューリングとそれらの間の切り替えによるシミュレーション(Aを少し、Bを少し、Aを少しなど)

それで、もしasync- awaitそれらのどちらもしないなら、それはどのようにしてアプリケーションを応答可能にすることができますか?スレッドが1つしかない場合、メソッドを呼び出すと、メソッドが完了するのを待ってから、他の処理を行うことになります。そのメソッド内のメソッドは、処理を進める前に結果を待つ必要があります。


17
IOタスクはCPUにバインドされていないため、スレッドを必要としません。非同期の主なポイントは、IOバウンドタスク中にスレッドをブロックしないことです。
juharr 2016年

24
@jdweng:いいえ、まったく違います。新しいスレッドを作成したとしても、それは新しいプロセスを作成することとは大きく異なります。
Jon Skeet

8
コールバックベースの非同期プログラミングを理解していれば、スレッドを作成せずにawait/がどのようにasync機能するかを理解できます。
user253751

6
それは正確にしない作るアプリケーションの応答性が、それはあなたのスレッドをブロックからあなたを思いとどまらない、応答しないアプリケーションの一般的な原因です。
オーウェン

6
@RubberDuck:はい、スレッドプールのスレッドを継続に使用できます。しかし、OPがここで想像する方法でスレッドを開始しているわけではありません。「この通常のメソッドを実行して、別のスレッドで実行してください-非同期です」とは言っていません。それよりずっと微妙だ。
ジョンスキート、

回答:


299

実際には、非同期/待機はそれほど魔法ではありません。完全なトピックはかなり広いですが、あなたの質問への迅速かつ完全な回答のために、私たちは管理できると思います。

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つに分割されていると言えます。

  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秒後、私たちが待っているのは完了です。そして、今起きていることは、メッセージループが参照しているキューにメッセージを配置することです。あなたが実行する」と、このコードは待機のすべてのコードです。
  6. メッセージループがそのメッセージに到達すると、基本的に、中断したところからそのメソッドを「再入力」し、直後にawait残りのメソッドの実行を続けます。このコードはメッセージループから再度呼び出されるため、このコードがasync/await適切に使用せずに長い時間実行すると、メッセージループが再びブロックされます。

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


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

.NETには、必ずしもそれ自体でスレッドを起動するとは限らないものの、依然として非同期である多くのものがあります。

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

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


わかりました、それであなたはその「幅広い話題のもの」のいくらかが欲しいですか?

さて、私たちのボタンのクリックについてRoslynを試してみましょう

Roslynを試す

ここで生成された完全なクラスにリンクするつもりはありませんが、それはかなり悲惨なものです。


10
つまり、基本的には、OPが「タスクをスケジュールし、それらを切り替えることによって並列実行をシミュレートする」と説明したことではないでしょうか。
Bergi、

4
@Bergi違います。実行は完全に並列です-非同期I / Oタスクは進行中であり、スレッドを続行する必要はありません(これはWindowsが登場するずっと前に使用されていたものです-MS DOSも非同期I / Oを使用しましたが、そうではありませんでしたマルチスレッドを使用してください!)もちろん、あなたがそれを説明する方法でも使用await できますが、一般的にはそうではありません。コールバックのみが(スレッドプールで)スケジュールされます-コールバックと要求の間に、スレッドは必要ありません。
Luaan 2016年

3
質問は特に非同期/待機に関するものであり、独自のスレッドを作成しないため、そのメソッドが何をするのかについてあまりはっきりと話したくないのはそのためです。明らかに、スレッドが完了するのを待つために使用できます。
ラッセV.カールセン2016年

5
@ LasseV.Karlsen-私はあなたの素晴らしい答えを取り入れていますが、私はまだ1つの詳細にこだわっています。手順4のように、メッセージポンプがポンプを継続できるようにするイベントハンドラーが存在することを理解していますが、「2秒かかるもの」は別のスレッド上にない場合、いつどこで実行され続けますか?それはUIスレッド上で実行した場合、それを実行する必要があるため、それが実行している間、それはとにかくメッセージポンプをブロックするいくつかの時間を同じスレッドに.. ... [続ける]
rory.ap

3
メッセージポンプでの説明が好きです。コンソールアプリケーションやWebサーバーのようなメッセージポンプがない場合、説明はどのように異なりますか?メソッドの再トレースはどのように達成されますか?
Puchacz

94

私はそれをブログの記事「スレッドはありません」で完全に説明しています。

要約すると、最近のI / OシステムはDMA(Direct Memory Access)を多用しています。ネットワークカード、ビデオカード、HDDコントローラー、シリアル/パラレルポートなどに特別な専用プロセッサーがあります。これらのプロセッサーはメモリバスに直接アクセスし、CPUから完全に独立して読み取り/書き込みを処理します。CPUは、データを含むメモリ内の場所をデバイスに通知するだけでよく、デバイスが割り込みを発生させて、読み取り/書き込みが完了したことをCPUに通知するまで、独自の処理を実行できます。

操作が実行中になると、CPUが行う作業はなく、スレッドもありません。


明確にするために.. async-awaitを使用すると何が起こるかを理解しています。スレッドなしの作成について-あなたが言ったようにデバイス自体へのI / Oリクエストにのみスレッドはなく、リクエスト自体を処理する独自のプロセッサがありますか?すべてのI / O要求がそのような独立したプロセッサで処理されると想定できますか?つまり、CPUバインドアクションでのみTask.Runを使用しますか?
Yonatan Nir ​​2017

@YonatanNir:プロセッサーを分離するだけではありません。あらゆる種類のイベント駆動型の応答は当然非同期です。Task.RunCPUにバインドされたアクションに最適ですが、他にもいくつかの用途があります。
スティーブンクリアリー2017

1
あなたの記事を読み終えましたが、OSの低レベルの実装に精通していないので、理解できない基本的なことがまだあります。私はあなたが書いたところまで書いたものを手に入れました:「書き込み操作は現在「進行中」です。それを処理しているスレッドの数は?なし。」。スレッドがない場合、スレッド上にない場合、操作自体はどのように行われますか?
Yonatan Nir ​​2017

5
これは何千もの説明で欠けている部分です!!! 実際には、I / O操作を伴うバックグラウンドで作業をしている人がいます。それはスレッドではなく、その仕事をしている別の専用ハードウェアコンポーネントです!
the_dark_destructor 2017年

2
@PrabuWeerasinghe:コンパイラーは、状態変数とローカル変数を保持する構造体を作成します。awaitが譲る(つまり、呼び出し元に戻る)必要がある場合、その構造体はボックス化され、ヒープ上に存在します。
Stephen Cleary

87

コンピュータが一度に複数のことを実行しているように見える唯一の方法は、(1)実際には一度に複数のことを実行している、(2)タスクをスケジュールしてそれらを切り替えることでシミュレーションすることです。したがって、async-awaitがこれらのどちらも行わない場合

待っていることはそれらのどちらも行いませ。の目的は、同期コードを魔法のように非同期awaitすることではないことを覚えておいてください。これは、非同期コードを呼び出すときに、同期コードの記述に使用するのと同じ手法を使用できるようにするためです。Awaitは、高レイテンシ操作を使用するコードを低レイテンシ操作を使用するコードのようにすることです。これらの高レイテンシ操作は、スレッド上にある場合もあれば、専用ハードウェア上にある場合もあり、作業を細かく分割して、後でUIスレッドで処理するためにメッセージキューに入れる場合もあります。彼らは非同期を達成するために何かをしていますが、彼らはそれをしているものです。Awaitでは、その非同期性を利用できます。

また、3つ目のオプションがないと思います。私たちの高齢者-ラップ音楽を聴く今日の子供たちは私の芝生から降りる必要があります-など、1990年代初頭のWindowsの世界を思い出してください。マルチCPUマシンもスレッドスケジューラもありませんでした。2つのWindowsアプリを同時に実行したかったので、譲らなければなりませんでした。マルチタスクは協調的でした。OSはプロセスに実行を開始することを通知します。OSの動作に問題があると、他のすべてのプロセスがサービスされなくなります。それは降伏するまで実行され、何とかしてれ、次にOSが制御を戻したときに、か、中断したところから再開する方法を知るがあります。。シングルスレッドの非同期コードは、 "yield"ではなく "await"を使用したものによく似ています。待機とは、「ここで中断したところを覚えておき、しばらく他の人に走らせます。待機しているタスクが完了したら、折り返し電話をし、中断したところから再開します。」Windows 3日間と同じように、それによってアプリの応答性が向上する方法を理解できると思います。

メソッドを呼び出すとは、メソッドが完了するのを待つことを意味します。

あなたが見逃している鍵があります。 メソッドは、その処理が完了する前に戻る可能性があります。それがまさに非同期の本質です。メソッドが戻ると、「この作業は進行中です。完了したら何をすべきか教えてください」というタスクを返します。メソッドの作業は、それが戻ったとしても行われません。

awaitオペレーターの前に、スイスチーズに通されたスパゲッティのようなコードを記述して、完了後に行う必要があるという事実に対処する必要がありましたが、戻りと完了は非同期化されました。Awaitを使用すると、実際に同期することなく、戻りと完了が同期しているように見えるコードを作成できます。


他の最新の高水準言語も同様の明示的な協調動作をサポートします(つまり、関数は何かを実行し、[値/オブジェクトを呼び出し元に送信する可能性があります]、コントロールが返されたときに中断したところから続行します[追加の入力が提供されている可能性があります] )。1つには、Pythonのジェネレーターは非常に大きいです。
JAB、2016年

2
@JAB:もちろんです。ジェネレーターはC#では "イテレーターブロック"と呼ばれ、yieldキーワードを使用します。asyncC#のメソッドと反復子はどちらもcoroutineの形式です。これは、現在の操作を後で再開するために一時停止する方法を知っている関数の総称です。最近、多くの言語にコルーチンまたはコルーチンのような制御フローがあります。
Eric Lippert、2016年

1
生成のアナロジーは良いものです-1 つのプロセス内での協調的なマルチタスクです。(それにより、システム全体の協調マルチタスクのシステム安定性の問題を回避します)
user253751

3
IOに使用されている「CPU割り込み」の概念は、多くのモデム「プログラマ」について知られていないため、スレッドはIOの各ビットを待機する必要があると考えています。
Ian Ringrose 2016年

Webクライアントの@EricLippert非同期メソッドは、実際には、追加のスレッドを作成し、ここを参照してくださいstackoverflow.com/questions/48366871/...
KevinBui

27

長い間、並行処理にはスレッドが必要であるとも信じていたので、誰かがこの質問をしたことを本当に嬉しく思います。最初にイベントループを見たとき、それは嘘だと思った。私は、「このコードがシングルスレッドで実行される場合、このコードを同時に実行することはできない」と考えました。これは、同時実行性と並列処理の違いを理解するための闘いをすでに終えた後のことです。

自分で調べた後、ようやく不足している部分を見つけましたselect()。具体的には、別の名前の下で様々なカーネルによって実装IO多重、: 、select()poll()、。epoll() kqueue()これらはシステムコールであり、実装の詳細は異なりますが、監視するファイル記述子のセットを渡すことができます。次に、監視されているファイル記述子の1つが変更されるまでブロックする別の呼び出しを行うことができます。

したがって、一連のIOイベント(メインイベントループ)で待機し、完了した最初のイベントを処理してから、制御をイベントループに戻すことができます。すすぎ、繰り返します。

これはどのように作動しますか?まあ、簡単な答えは、それはカーネルとハードウェアレベルの魔法だということです。コンピューターにはCPU以外にも多くのコンポーネントがあり、これらのコンポーネントは並列に動作できます。カーネルはこれらのデバイスを制御し、それらと直接通信して特定の信号を受信できます。

これらのIO多重化システムコールは、node.jsやTornadoなどのシングルスレッドイベントループの基本的な構成要素です。あなたの場合はawait機能には、特定のイベント(その関数の完了)を監視して、メインイベントループに制御バックを得ています。監視しているイベントが完了すると、関数は(最終的に)中断したところから再開します。このように計算を一時停止および再開できる関数は、コルーチンと呼ばれます。


25

awaitタスクasync使用するスレッドではなく。

フレームワークには、Taskオブジェクトの形式でいくつかの作業を実行する準備ができているスレッドのプールがあります。タスクをプールに送信するということは、タスクアクションメソッドを呼び出すために、空いている既存の1スレッドを選択することを意味します。タスクを
作成するこれまでの方法より速く、新しいスレッドを作成するよりも、新しいオブジェクトを作成する問題です。

与えられたタスクを取り付けることができる継続をそれは新しいですが、それにタスクスレッドが終了した後に実行されるオブジェクト。

以来async/await使用タスクは、彼らはありませんよ、新規作成したスレッドを。


割り込みプログラミング手法は現代のすべてのOSで広く使用されていますが、ここでは関係ないと思います。
を使用して、1つのCPUで 2つのCPU結合タスクを並行して(実際にはインターリーブして)実行 できますaysnc/await
これは、OSがキューイングIORPをサポートしているという事実だけでは説明できません。


前回、コンパイラーが変換したasyncメソッドをDFAにチェックしたとき、作業は複数のステップに分かれており、各ステップは1つのawait命令で終了しています。
await始まりタスクし、それを次のステップを実行するための継続を添付してください。

概念例として、以下に擬似コードの例を示します。
わかりやすくするため、また詳細をすべて正確に覚えていないため、物事は簡略化されています。

method:
   instr1                  
   instr2
   await task1
   instr3
   instr4
   await task2
   instr5
   return value

こんな感じに変身

int state = 0;

Task nextStep()
{
  switch (state)
  {
     case 0:
        instr1;
        instr2;
        state = 1;

        task1.addContinuation(nextStep());
        task1.start();

        return task1;

     case 1:
        instr3;
        instr4;
        state = 2;

        task2.addContinuation(nextStep());
        task2.start();

        return task2;

     case 2:
        instr5;
        state = 0;

        task3 = new Task();
        task3.setResult(value);
        task3.setCompleted();

        return task3;
   }
}

method:
   nextStep();

1実際には、プールはタスク作成ポリシーを持つことができます。


16

私はエリックリッペルトやラッセV.カールセンなどと競争するつもりはありません。この質問の別の側面に注目したいのですが、明示的に言及されていなかったと思います。

await独自に使用しても、アプリが魔法のように反応するわけではありません。UIスレッドから待機しているメソッドで何をしてもブロックする場合でも、非待機バージョンと同じようにUIをブロックします

新しいスレッドを生成するか、完了ポートのようなものを使用するように、待機可能なメソッドを具体的に記述する必要があります(現在のスレッドで実行を返し、完了ポートが通知されるたびに継続のために何かを呼び出します)。しかし、この部分は他の答えでよく説明されています。


3
そもそも競争ではありません。コラボレーションです!
Eric Lippert、2016年

16

これが私がこれをすべて見る方法です、それは超技術的に正確ではないかもしれませんが、それは私を助けます、少なくとも:)。

マシン上で行われる処理(計算)には、基本的に2つのタイプがあります。

  • CPUで発生する処理
  • 他のプロセッサ(GPU、ネットワークカードなど)で発生する処理をIOと呼びましょう。

したがって、ソースコードを作成する場合、コンパイル後、使用するオブジェクトに応じて(これは非常に重要です)、処理は次のようになります。 CPUバウンドまたはIOバウンドになりますになり、実際には、次の組み合わせにバインドできます。両方とも。

いくつかの例:

  • のWriteメソッドを使用する場合 FileStreamオブジェクト(ストリーム)、処理は1%のCPUに制限され、99%のIOに制限されます。
  • のWriteメソッドを使用する場合 NetworkStreamオブジェクト(ストリーム)、処理は1%のCPUに制限され、99%のIOに制限されます。
  • のWriteメソッドを使用する場合 Memorystreamオブジェクト(ストリーム)、処理は100%CPUバウンドになります。

ご覧のとおり、オブジェクト指向のプログラマの観点から、私は常にアクセスしていますが Streamオブジェクトにますが、その下で何が起こるかは、オブジェクトの最終的なタイプに大きく依存している可能性があります。

物事を最適化するために、コードを実行できると便利な場合があります ために、可能な場合や必要な場合は、を並行(非同期という言葉は使用していません)。

いくつかの例:

  • デスクトップアプリで、ドキュメントを印刷したいのですが、それを待ちたくありません。
  • 私のWebサーバーは同時に多くのクライアントを処理し、各クライアントは(シリアル化されていない)並列で自分のページを取得します。

非同期/待機する前に、基本的にこれに対する2つのソリューションがありました。

  • スレッド。ThreadクラスとThreadPoolクラスを使用することで、比較的簡単に使用できました。スレッドはCPUバウンドのみです。
  • 「古い」Begin / End / AsyncCallback非同期プログラミングモデル。これは単なるモデルであり、CPUバインドかIOバインドかはわかりません。SocketクラスまたはFileStreamクラスを見ると、I / Oバウンドです。これはクールですが、ほとんど使用しません。

async / awaitは、タスクコンセプトに基づく一般的なプログラミングモデルにすぎません。CPUにバインドされたタスクでは、スレッドやスレッドプールよりも少し使いやすく、古いBegin / Endモデルよりもはるかに使いやすいです。ただし、Undercoversは、両方の機能を完全に備えた非常に洗練されたラッパーです。

つまり、本当の勝利は主にIO Boundタスクにあります場合、CPUを使用しないタスクであるにありますが、非同期/待機はまだプログラミングモデルにすぎず、最終的に処理がどのように/どこで行われるかを判断するのに役立ちません。

これは、クラスがTaskオブジェクトを返すメソッド「DoSomethingAsync」を持っているためではなく、CPUにバインドされている(つまり、キャンセルトークンパラメーターがない場合は特に役に立たない可能性がある)、またはIOバウンドであると推測できることを意味します(これはおそらく必須であることを意味します)、または両方の組み合わせ(モデルは非常にバイラルであるため、結合と潜在的な利点は、結局、非常に混合され、それほど明白ではない可能性があります)。

したがって、私の例に戻って、MemoryStreamでasync / awaitを使用して書き込み操作を実行すると、CPUバインドが維持されます(おそらく、その恩恵を受けません)。


1
これは、CPUにバインドされた作業にtheadpoolを使用することは非常に適切な回答であり、TPスレッドを使用してIO操作をオフロードする必要があるという意味では不十分です。CPUにバインドされた作業imoはもちろん警告でブロックする必要があり、複数のスレッドの使用を妨げるものはありません。
davidcarr 2018

3

他の回答の要約:

Async / awaitは、主にIOバウンドタスク用に作成されます。これらを使用することで、呼び出しスレッドのブロックを回避できるためです。それらの主な用途は、IOバウンド操作でスレッドがブロックされることが望ましくないUIスレッドでの使用です。

非同期はそれ自身のスレッドを作成しません。呼び出しメソッドのスレッドを使用して、待機メソッドが見つかるまで非同期メソッドを実行します。その後、同じスレッドが、非同期メソッド呼び出しを超えて、残りの呼び出しメソッドを実行し続けます。呼び出されたasyncメソッド内で、awaitableから戻った後、スレッドプールのスレッドで継続を実行できます。別のスレッドが登場するのはこの場所だけです。


良い要約ですが、全体像を把握するには、さらに2つの質問に答える必要があると思います。1.待機中のコードが実行されるスレッドは何ですか?2.上記のスレッドプールを制御/構成するのはだれですか-開発者ですか、それともランタイム環境ですか?
ストイケ2018

1.この場合、ほとんどの待機中のコードは、CPUスレッドを使用しないIOバウンド操作です。CPUバインド操作の待機を使用したい場合は、別のタスクを生成できます。2.スレッドプール内のスレッドは、TPLフレームワークの一部であるタスクスケジューラによって管理されます。
vaibhav kumar 2018

2

ボトムアップで説明してみます。多分誰かがそれが役立つと思います。PascalのDOSで簡単なゲームを作ったとき、私はそこにいて、それをやり直し、それを再発明しました(古き良き時代...)

つまり...すべてのイベント駆動型アプリケーションでは、次のようなイベントループが内部にあります。

while (getMessage(out message)) // pseudo-code
{
   dispatchMessage(message); // pseudo-code
}

フレームワークは通常、この詳細を非表示にしますが、そこにあります。getMessage関数は、イベントキューから次のイベントを読み取るか、マウスムーブ、キーダウン、キーアップ、クリックなどのイベントが発生するまで待機します。次に、dispatchMessageが適切なイベントハンドラーにイベントをディスパッチします。次に、ループを終了してアプリケーションを終了する終了イベントが来るまで、次のイベントを待ちます。

イベントハンドラーは高速に実行する必要があるため、イベントループがより多くのイベントをポーリングし、UIの応答性が維持されます。ボタンのクリックがこのような高価な操作をトリガーするとどうなりますか?

void expensiveOperation()
{
    for (int i = 0; i < 1000; i++)
    {
        Thread.Sleep(10);
    }
}

まあ、コントロールが関数内に留まり、10秒の操作が完了するまで、UIは応答しなくなります。この問題を解決するには、タスクをすばやく実行できる小さな部分に分割する必要があります。つまり、1つのイベントですべてを処理することはできません。作業のほんの一部を実行してから、継続を要求するために別のイベントをイベントキューに投稿する必要があります。

したがって、これを次のように変更します。

void expensiveOperation()
{
    doIteration(0);
}

void doIteration(int i)
{
    if (i >= 1000) return;
    Thread.Sleep(10); // Do a piece of work.
    postFunctionCallMessage(() => {doIteration(i + 1);}); // Pseudo code. 
}

この場合、最初の反復のみが実行され、次の反復を実行するためにメッセージをイベントキューにポストして戻ります。この例のpostFunctionCallMessage疑似関数が「call this function」イベントをキューに入れると、イベントディスパッチャーは、それに到達したときにそれを呼び出します。これにより、他のすべてのGUIイベントを処理しながら、長時間実行されている作業の一部を継続的に実行することもできます。

この長時間実行タスクが実行されている限り、その継続イベントは常にイベントキューにあります。あなたは基本的にあなた自身のタスクスケジューラを発明しました。キュー内の継続イベントは、実行中の「プロセス」です。実際には、これはオペレーティングシステムが行うことですが、継続イベントの送信とスケジューラループへの戻りは、OSがコンテキスト切り替えコードを登録したCPUのタイマー割り込みを介して行われるので、気にする必要はありません。しかし、ここでは独自のスケジューラーを作成しているため、これまではそれを気にする必要があります。

そのため、実行時間の長いタスクをGUIと並行して単一のスレッドで実行し、小さなチャンクに分割して継続イベントを送信することができます。これがTaskクラスの一般的な考え方です。これは作業の一部を表し、それ.ContinueWithを呼び出すときに、現在の部分が終了したときに次の部分として呼び出す関数を定義します(その戻り値が継続に渡されます)。このTaskクラスはスレッドプールを使用します。ここで、最初に示したのと同様の作業を実行するために待機している各スレッドにイベントループがあります。このようにすると、何百万ものタスクを並行して実行できますが、それらを実行するスレッドはほんのわずかです。しかし、単一のスレッドでも同様に機能します。タスクが適切に細かく分割されている限り、それぞれが並列で実行されているように見えます。

しかし、すべてのチェーンを細かく分割する作業を手動で行うのは面倒な作業であり、バックグラウンドタスクコード全体が基本的に.ContinueWith混乱しているため、ロジックのレイアウトを完全に混乱させます。だから、コンパイラはあなたを助ける場所です。バックグラウンドでこのすべての連鎖と継続を行います。あなたが言うときawait、あなたが「ここで停止し、継続タスクとしての機能の残りの部分を追加します」というコンパイラに伝える伝えます。コンパイラーが残りを処理するので、あなたがする必要はありません。


0

実際、async awaitチェーンはCLRコンパイラによって生成されるステートマシンです。

async await ただし、TPLがスレッドプールを使用してタスクを実行するスレッドは使用します。

アプリケーションがブロックされない理由は、ステートマシンが実行するコルーチンを決定し、繰り返し、チェックし、再度決定できるためです。

参考文献:

async&awaitは何を生成しますか?

非同期待機と生成されたStateMachine

非同期のC#とF#(III。):どのように機能しますか?-Tomas Petricek

編集

はい。私の説明は間違っているようです。ただし、ステートマシンはの重要な資産であることを指摘する必要がありasync awaitます。非同期I / Oを取り込んでも、操作が完了したかどうかを確認するヘルパーが必要なため、ステートマシンが必要であり、同時に非同期で実行できるルーチンを決定します。


0

これは直接質問に答えるものではありませんが、興味深い追加情報だと思います。

Async and awaitは、それ自体では新しいスレッドを作成しません。ただし、非同期待機を使用する場所によっては、待機前の同期部分が、待機後の同期部分とは別のスレッドで実行される場合があります(たとえば、ASP.NETとASP.NETコアの動作は異なります)。

UIスレッドベースのアプリケーション(WinForms、WPF)では、前後で同じスレッドになります。ただし、スレッドプールスレッドで非同期を使用すると、待機の前後のスレッドが同じにならない場合があります。

このトピックに関するすばらしいビデオ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.