web-dev-qa-db-ja.com

非同期プログラミングとマルチスレッドの違いは何ですか?

私はそれらが基本的に同じものであると思いました - プロセッサ間でタスクを分割するプログラムを書く(2つ以上のプロセッサを持っているマシンで)。それから私は読んでいます https://msdn.Microsoft.com/en-us/library/hh191443.aspx 、それは言う

非同期メソッドは、ノンブロッキング操作を目的としています。非同期メソッド内の待機式は、待機中のタスクの実行中に現在のスレッドをブロックしません。代わりに、この式はメソッドの残りの部分を継続として登録し、asyncメソッドの呼び出し元に制御を返します。

Asyncキーワードとawaitキーワードによって追加のスレッドが作成されることはありません。非同期メソッドはそれ自身のスレッドでは実行されないので、非同期メソッドはマルチスレッドを必要としません。メソッドは現在の同期コンテキストで実行され、メソッドがアクティブな場合にのみスレッドの時間を使用します。 Task.Runを使用すると、CPUに関連する作業をバックグラウンドスレッドに移動できますが、バックグラウンドスレッドは、結果が利用可能になるのを待っているプロセスには役立ちません。

誰かがそれを私に代わって英語に翻訳できるかどうか疑問に思います。それは非同期性(つまり単語ですか?)とスレッド化の間の区別を引き出すように思われ、非同期タスクを持つがマルチスレッド化を持たないプログラムを持つことができることを意味します。

これでpgの例のような非同期タスクの概念がわかりました。 Jon Skeetの467C#の深さ、第3版

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

asyncキーワードは、「という意味です。この関数は、呼び出されるたびに、呼び出される呼び出しの後に完了する必要があるコンテキストでは呼び出されません。

言い換えれば、いくつかのタスクの途中でそれを書く

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

DisplayWebsiteLength()xyとは無関係であるため、DisplayWebsiteLength()は "バックグラウンドで"実行されます。

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

これは明らかに愚かな例ですが、私は正しいのでしょうか、それともまったく混乱しているのでしょうか。

(また、sendereが上記の関数の本体で使用されていない理由についても混乱しています。)

149
user5648283

あなたの誤解は極めて一般的です。マルチスレッドと非同期は同じものであると多くの人が教えていますが、そうではありません。

アナロジーは通常役に立ちます。あなたはレストランで料理をしています。卵と乾杯の注文が入ってきました。

  • 同期:あなたは卵を調理し、次にあなたはトーストを調理します。
  • 非同期、シングルスレッド:卵の調理を開始してタイマーを設定します。トーストクッキングを始め、タイマーをセットします。両方とも調理している間、台所を掃除します。タイマーが切れるとき、あなたは卵をトースターから熱とトーストから外して、それらを出します。
  • 非同期、マルチスレッド:卵料理用とトースト料理用に、料理人をさらに2人雇います。今、あなたは料理人が資源を共有するときにそれらが台所で互いに衝突しないように料理人を調整する問題を抱えています。そしてあなたはそれらを払わなければなりません。

それでは、マルチスレッドが非同期の一種に過ぎないことは意味がありますか。 スレッド化はワーカーに関するものです。非同期はタスクに関するものです。マルチスレッドワークフローでは、タスクをワーカーに割り当てます。非同期シングルスレッドワークフローでは、一部のタスクが他のタスクの結果に依存するタスクのグラフがあります。各タスクが完了すると、完了したばかりのタスクの結果を考慮して、実行可能な次のタスクをスケジュールするコードを呼び出します。しかし、あなたは(うまくいけば)すべてのタスクを実行するために1人の作業員だけを必要とし、タスクごとに1人の作業員は必要ありません。

多くのタスクがプロセッサに依存しないことを理解するのに役立ちます。プロセッサに束縛されたタスクでは、プロセッサと同じ数のワーカ(スレッド)を雇い、各ワーカに1つのタスクを割り当て、各ワーカに1つのプロセッサを割り当て、各プロセッサに結果を計算する以外何もしないことを任せます。できるだけ早く。しかし、プロセッサで待機していないタスクの場合は、ワーカーをまったく割り当てる必要はありません。結果が得られるというメッセージが届くのを待つだけで、待っている間に何か他のことをする。そのメッセージが到着したら、次に実行しなければならないことが、タスクリストの次のタスクとして完了したタスクの継続をスケジュールすることができます。

それでは、Jonの例をさらに詳しく見てみましょう。何が起こるのですか?

  • 誰かがDisplayWebSiteLengthを呼び出します。誰?気にしません。
  • ラベルを設定し、クライアントを作成し、クライアントに何かを取得するように依頼します。クライアントは何かを取得するタスクを表すオブジェクトを返します。その作業は進行中です。
  • 別のスレッドで進行中ですか?おそらくそうではありません。なぜスレッドがないのか--- Stephenの記事 を読んでください。
  • 今、私たちはそのタスクを待っています。何が起こるのですか?タスクが作成されてから待っている間にタスクが完了したかどうかを確認します。そうであれば、結果を取得して実行を続けます。それが完了していないとしましょう。 このメソッドの残りの部分をそのタスクの継続として登録し、を返します。
  • 制御は呼び出し側に戻りました。それは何をするためのものか?欲しいものは何でも。
  • タスクが完了したとします。どうやってそれをしたの?たぶんそれは別のスレッドで、あるいはたぶん私たちが戻ってきた呼び出し元で実行されていたのでしょうか。それにもかかわらず、これでタスクが完了しました。
  • 完了したタスクは正しいスレッド、おそらくonly thread - にタスクの継続を実行するように要求します。
  • 制御はすぐに待っていたところのメソッドに戻ります。これでis結果が得られるので、textを代入して残りのメソッドを実行できます。

私のたとえと同じです。誰かがあなたに文書を求めます。あなたは文書のために郵便で送り出し、そして他の仕事をし続けます。それがメールに届いたとき、あなたは合図され、あなたがそれを感じたとき、あなたはワークフローの残りの部分をする - 封筒を開いて、何でも配達料金を払う。あなたはそのために他の労働者を雇う必要はありません。

387
Eric Lippert

ブラウザ内のJavascriptは、スレッドを持たない非同期プログラムの好例です。

複数のコードが同じオブジェクトに同時に触れることを心配する必要はありません。各関数は、他のJavaScriptがそのページで実行される前に実行を終了します。

ただし、AJAXリクエストのようなことをしているときは、コードがまったく実行されていないので、そのリクエストが返されてそれに関連するコールバックが呼び出されるまで、他のJavaScriptはclickイベントなどに応答できます。 AJAXリクエストが戻ったときにこれらの他のイベントハンドラの1つがまだ実行されている場合、そのハンドラはそれらが完了するまで呼び出されません。必要な情報が得られるまで、実行していることを効果的に一時停止することは可能ですが、実行されているJavaScript「スレッド」は1つだけです。

C#アプリケーションでは、UI要素を扱っているときはいつでも同じことが起こります - UIスレッドを使用しているときにのみUI要素との対話が許可されます。ユーザーがボタンをクリックし、ディスクから大きなファイルを読み取って応答したい場合、経験の浅いプログラマーがclickイベントハンドラ自体の中でファイルを読み取るのを間違える可能性があります。そのスレッドが解放されるまでそれ以上のクリック、ホバー、またはその他のUI関連のイベントに応答することは許可されていないため、ファイルはロードを終了しました。

この問題を回避するためにプログラマが使用するかもしれない1つのオプションは、ファイルをロードするための新しいスレッドを作成し、ファイルがロードされるときにUI要素を更新できるように残りのコードを再度実行する必要があることをそのスレッドのコードに伝えることです。ファイルの内容に基づいて最近まで、このアプローチはC#ライブラリと言語を容易にするものであったため非常に人気がありましたが、基本的に必要以上に複雑になりました。

ハードウェア/オペレーティングシステムレベルでファイルを読み込むときにCPUが何をしているのかを考えると、基本的にはディスクからメモリにデータの一部を読み込み、「割り込み」でオペレーティングシステムをヒットさせる命令を発行します。読み取りが完了しました。つまり、ディスク(または実際には任意の入出力)からの読み取りは、本質的に非同期操作です。そのI/Oが完了するのを待つスレッドの概念は、プログラミングを容易にするためにライブラリ開発者が作成した抽象概念です。それは必要はありません。

現在、.NETのほとんどのI/O操作には、対応する...Async()メソッドを呼び出すことができます。このメソッドは、ほとんどすぐにTaskを返します。このTaskにコールバックを追加して、非同期操作の完了時に実行したいコードを指定できます。また、そのコードをどのスレッドで実行するかを指定したり、非同期タスクを取り消すことを決定したときに非同期操作でチェックできるトークンを指定して、作業を迅速に停止することもできます。そして優雅に。

async/awaitキーワードが追加されるまでは、C#はコールバックコードがどのように呼び出されるかについてより明確になっていました。コールバックはタスクに関連付けられたデリゲートの形式だったからです。コードの複雑さを回避しながら...Async()操作を使用する利点を引き続き得るために、async/awaitはそれらのデリゲートの作成を抽象化します。しかし、それらはコンパイルされたコードにまだ存在しています。

そのため、UIイベントハンドラawaitをI/O操作にして、UIスレッドを解放して他のことを実行し、ファイルの読み取りが完了したら自動的にUIスレッドに戻ることができます。新しいスレッドを作成します。

15