47

私はasync / awaitをいつ読んでいたかTask.Yield役に立つかもしれませんし、出会ったこの郵便受け。私はその記事から以下に関して質問がありました:

あなたがasync / awaitを使うとき、その方法があなたに保証されるという保証はありません。   待っているときに電話をかけるFooAsync()実際には非同期に実行されます。   内部実装は完全にを使って自由に戻ることができます   同期パス

おそらく私の頭の中で非同期の定義が並んでいないので、これは私には少し不明瞭です。

私の考えでは、私は主にUI開発を行っているので、非同期コードはUIスレッド上では実行されず、他のスレッド上では実行されないコードです。私が引用したテキストで推測しているように、メソッドがどのスレッドでブロックされても(たとえばスレッドプールスレッドであっても)、メソッドは本当に非同期ではありません。

質問:

私がCPUに縛られている長時間実行されているタスクを持っているなら(それがたくさんの難しい数学をしているとしよう)、それからそのタスクを非同期的に実行することはいくつかのスレッドを正しくブロックしなければなりませんか?何かが実際に数学をしなければなりません。私がそれを待っているなら、それからいくつかのスレッドはブロックされています。

真に非同期的なメソッドの例は何ですか?また、それらは実際にどのように機能しますか?ハードウェア機能を利用してスレッドがブロックされることのないI / O操作に限定されていますか。


  • 必須の読書:blog.stephencleary.com/2013/11/there-is-no-thread.html - NWard
  • 私はあなたの質問が好きですが、タイトルの「.Net / C#の非同期メソッド」を参照してください。あまりにも一般的なIMOです。 - Julian
  • @ Julian私はOPの質問をより具体的にするために更新しました - Brian Ogden
  • CPUのバウンドであれば、そのCPUはスレッド上で実行されています。はい、すべてのコードはスレッド上で実行されます。ネットワークからのパケットを待っている場合は、それはコードではありませんしたがって、スレッド上で実行する必要はありません。 (待機を開始し、待機を停止するコードがどこかにありますが、実際の待機はコードではありません) - immibis

7 답변


105

おそらく私の頭の中で非同期の定義が並んでいないので、これは私には少し不明瞭です。

説明を求めてくれてありがとう。

私の考えでは、私は主にUI開発を行っているので、非同期コードはUIスレッド上では実行されず、他のスレッド上では実行されないコードです。

その信念は一般的ですが偽です。非同期コードが任意の2番目のスレッドで実行されるという要件はありません。

あなたが朝食を作っていると想像してください。あなたはトースターにトーストを入れて、トーストが飛び出すのを待っている間に、昨日からあなたのメールを見て、いくつかの請求書を払い、そしてちょっと、トーストが飛び出しました。あなたはその請求書の支払いを終えてから、あなたのトーストを飲みます。

あなたのトースターを見るためにどこであなたは2人目の労働者を雇いましたか?

あなたはしませんでした。糸は労働者です。非同期ワークフローは1つのスレッドですべて発生する可能性があります。非同期ワークフローのポイントは、避けるあなたがそれを避けることができるならばもっと多くの労働者を雇う。

私がCPUに縛られている長時間実行されているタスクを持っているなら(それがたくさんの難しい数学をしているとしよう)、それからそのタスクを非同期的に実行することはいくつかのスレッドを正しくブロックしなければなりませんか?何かが実際に数学をしなければなりません。

ここで、私はあなたに解決するのが難しい問題をあげます。これは100の数字の列です。手でそれらを合計してください。それで、あなたは最初のものを2番目のものに加えて、合計を作ります。それから3番目に現在の合計を加え、合計を得ます。それで、ああ、地獄、2ページ目の数字がなくなっています。あなたがどこにいたか覚えていて、そして乾杯してください。ああ、乾杯が乾杯している間に、手紙が残りの数字と共に届いた。トーストのバター焼きが終わったら、続けてこれらの数字を足し合わせ、次に自由な時間があるときはトーストを食べることを忘れないでください。

番号を追加するためにあなたが他の労働者を雇った部分はどこですか?計算量の多い作業は同期的である必要はなく、スレッドをブロックする必要もありません。。計算作業を潜在的に非同期にすることは、それを停止し、あなたがいた場所を覚えていて、何か他のことをし、何をすべきか覚えていることですその後そして中断したところから再開します。

今では確かに可能何もしないで番号を追加してから解雇される2人目の労働者を雇うため。そして、あなたはその労働者に「あなたはやりましたか?」と尋ねることができます。答えが「いいえ」の場合は、サンドイッチを作り終えるまでサンドイッチを作ることができます。そのようにあなたと労働者の両方は忙しいです。しかし、ありません要件その非同期は複数の作業者を含みます。

私がそれを待っているなら、それからいくつかのスレッドはブロックされています。

ダメダメダメ。これはあなたの誤解の最も重要な部分です。await「このジョブを非同期的に開始する」という意味ではありません。await「ここには非同期で生成された結果があり、利用できない可能性があります。利用できない場合は、このスレッドでやるべき他の作業を見つけるだから私たちはではないスレッドをブロックしています。待って反対側あなたが言ったことのうち。

真に非同期的なメソッドの例は何ですか?また、それらは実際にどのように機能しますか?ハードウェア機能を利用してスレッドがブロックされることのないI / O操作に限定されていますか。

非同期作業にはカスタムハードウェアまたは複数のスレッドが含まれることがよくありますが、そうである必要はありません。

考えてはいけない労働者。について考えるワークフロー。非同期の本質はワークフローを細かく分割するそのようなあなたはそれらの部分が起こるべき順序を決定することができます、 その後各部分を順番に実行するしかし、互いに依存関係を持たない部分をインターリーブできるようにする

非同期ワークフローでは、簡単に検出できますパーツ間の依存関係が表現されているワークフロー内の場所。そのような部分はでマークされていますawait。それがの意味ですawait以下のコードは、ワークフローのこの部分が完了しているかどうかによって異なるため、完了していない場合は、実行する他のタスクを見つけて、タスクが完了したら後でここに戻ってきてください。全体的な要点は、将来必要な結果が生み出されている世界でさえ、労働者を働かせ続けることです。


  • 「スレッドを抽象化する」という概念の上にこのスレッドの抽象概念を作成したのは、少し面白いことです。 (割り込みやDPCのように)そして今、私たちは「ものをやるだけ」の抽象概念を構築しました。スレッドに基づきます。 - immibis
  • @immibis割り込みとDPCはどのように抽象化されていますか?それらはツールであり、抽象化ではありません。Task抽象化です。 - Euphoric
  • AFAIK JavaScriptはシングルスレッドであり、非同期性を重視しています。 - Stephen S
  • @Euphoric、コメントを読み直すことをお勧めします。割り込みやDPCは抽象化であるとは言わなかった。 - immibis

26

async / awaitを読んでいました

私は私をお勧めしますasyncイントロ

そしてTask.Yieldが役に立つかもしれないとき

ほとんどは決してない。私はユニットテストをするときそれが時々役に立つと思います。

私の考えでは、私は主にUI開発を行っているので、非同期コードはUIスレッド上では実行されず、他のスレッド上では実行されないコードです。

非同期コードはスレッドレスにすることができます

私が引用したテキストで推測しているように、メソッドがどのスレッドでブロックされても(たとえばスレッドプールスレッドであっても)、メソッドは本当に非同期ではありません。

私はそれが正しいと言うでしょう。どのスレッドもブロックしない(そして同期的でない)操作には、「真に非同期」という用語を使用します。また、「偽の非同期」という用語を使用することもあります。現れる非同期ですが、スレッドプールthreadを実行したりスレッドプールをブロックしたりするので、そのようにのみ動作します。

私がCPUに縛られている長時間実行されているタスクを持っているなら(それがたくさんの難しい数学をしているとしよう)、それからそのタスクを非同期的に実行することはいくつかのスレッドを正しくブロックしなければなりませんか?何かが実際に数学をしなければなりません。

はい;この場合は、同期APIを使用してその作業を定義し(同期作業なので)、それを使用してUIスレッドから呼び出すことができます。Task.Run例えば、

var result = await Task.Run(() => MySynchronousCpuBoundCode());

私がそれを待っているなら、それからいくつかのスレッドはブロックされています。

いいえ。スレッドプールスレッドはコードを実行するために使用され(実際にはブロックされない)、UIスレッドはそのコードが完了するのを非同期的に待っています(ブロックされていません)。

真に非同期的なメソッドの例は何ですか?また、それらは実際にどのように機能しますか?

NetworkStream.WriteAsync(間接的に)ネットワークカードに数バイトの書き込みを要求します。バイトを一度に1つずつ書き出し、各バイトが書き込まれるのを待つ責任を負うスレッドはありません。ネットワークカードがそれをすべて処理します。ネットワークカードがすべてのバイトの書き込みを完了すると、(最終的に)から返されたタスクを完了します。WriteAsync

ハードウェア機能を利用してスレッドがブロックされることのないI / O操作に限定されていますか。

入出力操作は簡単な例ですが、完全ではありません。もう1つの非常に簡単な例はタイマーです(例:Task.Delay)ただし、あらゆる種類の「イベント」を中心にして、本当に非同期のAPIを構築することもできます。


  • Task.Yieldは、待機中のタスクのインライン展開を効果的に防止するのに役立ちます。現在の(async)メソッドが戻った後で待機中のタスクを確実に実行したい場合は、待機中のタスク内でTask.Yieldを待機すると、待機中のタスクがスケジューラ上で効果的にさらに下に移動します(インライン化されません)。 - はい? - sjb-sjb
  • @ sjb-sjb:Task.Yield非同期を強制します、はい。しかし、いつこれを行う必要がありますか?ほとんどは決してない。単体テストを行うときに便利です。本番コードで必要と思われる場合は、本番コードの設計を詳細に検討し、どのような改善が可能かを確認してください。 - Stephen Cleary
  • ええ、私は一般的に同意しますが、それはまだ起こります。私は最近ここで説明するには複雑すぎる状況でそれに遭遇しました、しかし私はデザインが正当であったと言うでしょう。通常はRunを使用して必要な非同期性を取得できますが、現在の(UI)スレッドで実行する必要があるため、この状況ではそれを実行できませんでした。私は、UIスレッド上のものが間違った順番で起こっていて、Yieldを使って修正されることができる他の状況も見ました。 - sjb-sjb
  • 最初の例は、現在のメソッドが終了した後にUIスレッドで何かを実行することでした。そのためには、別の方法があります。つまり、Window.Current.Dispatcher.RunAsyncを使用することです。他の例としては、このメソッドの残りの部分が実行される前に、他の何かがUIスレッドで実行されるようにします(「thisメソッド」はUIコールバックです)。そのためにはTask.Yieldが最も簡単だと思いますが、別のメソッドを作成してDispatcher.RunAsyncを使用してスケジュールすることもできます。だから私はあなたが正しいと思います。私がTask.Yieldを考えていた場合にはDispatcher.RunAsyncを使うことができます。 - sjb-sjb
  • @ sjb-sjb:Task.YieldメッセージキューがWindows上で機能する方法のため、そのためには機能しません。 - Stephen Cleary

10

async / awaitを使用するとき、FooAsync()を待つときに呼び出すメソッドが実際に非同期的に実行されるという保証はありません。内部実装は完全同期パスを使用して自由に戻ることができます。

これは私には少し不明瞭です。   私の頭の中で非同期は並んでいません。

これは単に、非同期メソッドを呼び出すときに2つのケースがあることを意味します。

1つは、タスクをあなたに返した時点で、操作はすでに完了しているということです。これは同期パスです。 2つ目は、操作がまだ進行中であるということです - これは非同期パスです。

このコードを考えてみてください。これらのパスの両方が示されているはずです。キーがキャッシュにある場合は、同期的に返されます。それ以外の場合は、データベースへの呼び出しを行う非同期操作が開始されます。

Task<T> GetCachedDataAsync(string key)
{
    if(cache.TryGetvalue(key, out T value))
    {
        return Task.FromResult(value); // synchronous: no awaits here.
    }

    // start a fully async op.
    return GetDataImpl();

    async Task<T> GetDataImpl()
    {
        value = await database.GetValueAsync(key);
        cache[key] = value;
        return value;
    }
}

それを理解することで、理論的には、database.GetValueAsync()似たようなコードを持っていて、それ自体が同期的に戻ることができるかもしれません。きみの非同期パスは、100%同期して実行される可能性があります。しかし、コードは気にする必要はありません。async / awaitは両方のケースをシームレスに処理します。

私がCPUに縛られている長時間実行されているタスクを持っているなら(それがたくさんの難しい数学をしているとしよう)、それからそのタスクを非同期的に実行することはいくつかのスレッドを正しくブロックしなければなりませんか?何かが実際に数学をしなければなりません。私がそれを待っているなら、それからいくつかのスレッドはブロックされています。

ブロッキングは明確に定義された用語です。つまり、スレッドが何か(I / O、ミューテックスなど)を待っている間に実行ウィンドウが生成されたことを意味します。したがって、数学を実行しているスレッドはブロックされているとは見なされません。実際に作業を実行しています。

真に非同期的なメソッドの例は何ですか?また、それらは実際にどのように機能しますか?ハードウェア機能を利用してスレッドがブロックされることのないI / O操作に限定されていますか。

「真に非同期な方法」は単純にブロックしない方法です。それは通常I / Oを含むことになりますが、それはまた意味することができますawait現在のスレッドに別のものを使用したい場合(UI開発の場合のように)、または並列処理を導入しようとしている場合は、重い数学コードを入力します。

async Task<double> DoSomethingAsync()
{
    double x = await ReadXFromFile();

    Task<double> a = LongMathCodeA(x);
    Task<double> b = LongMathCodeB(x);

    await Task.WhenAll(a, b);

    return a.Result + b.Result;
}


6

このトピックはかなり広大であり、いくつかの議論が生じるかもしれません。ただし、asyncそしてawaitC#では、非同期プログラミングと見なされます。しかし、非同期性がどのように機能するかについてはまったく異なる議論です。 .NET 4.5までは、asyncやawaitというキーワードはなく、開発者たちは直接に対抗して開発しなければなりませんでした。タスクパラレルライブラリy(TPL)そこで開発者はいつ、どのようにして新しいタスクやスレッドを作成するかを完全に制御することができました。ただし、このトピックに関する専門家ではないため、アプリケーションがスレッド間の競合状態などによる深刻なパフォーマンス上の問題やバグを抱えている可能性があります。

.NET 4.5からは、非同期プログラミングへの新しいアプローチとともに、asyncキーワードとawaitキーワードが導入されました。 asyncキーワードとawaitキーワードによって追加のスレッドが作成されることはありません。非同期メソッドは独自のスレッドでは実行されないため、非同期メソッドはマルチスレッドを必要としません。このメソッドは現在の同期コンテキストで実行され、メソッドがアクティブな場合にのみスレッドの時間を使用します。あなたが使用することができますTask.Runしかし、バックグラウンドスレッドは、結果が利用可能になるのを待っているだけのプロセスには役立ちません。

非同期プログラミングに対する非同期ベースのアプローチは、ほとんどすべての場合において既存のアプローチよりも望ましいです。特に、この方法は、コードが単純で、競合状態から保護する必要がないため、IOバウンド操作ではBackgroundWorkerよりも優れています。このトピックについてもっと読むことができますここに

私は自分自身をC#の黒帯とは考えておらず、経験豊富な開発者も議論を重ねるかもしれませんが、原則として私はあなたの質問に答えられることを願っています。


  • 参考になるが、これはOPの質問には答えない。 OPの質問であるIMOは、C#非同期プログラミングの最近の歴史の要約と、Asyncを使ったC#Asynchronous Programmingに関するMSDNへのOPへのリンクを含むリンクを含む答えが、OPが探している答えではないという十分具体的なものです。私は、C#Asynchronous Programming with AsyncでMSDNを読んだ後でも、OPの質問がまだ残っているかもしれないことは明らかだと思います - Brian Ogden
  • asyncキーワードとawaitキーワードを使用しても追加のスレッドは作成されないが、それでもまだOPの質問に完全には答えられないと指摘したことをお勧めします。 - Brian Ogden
  • ある意味では、私はあなたが正しいと仮定します。ただし、IMOのこの質問には実際には答えられない、または少なくとも現在策定されている方法ではありません。 「重い数学」とは手段は、すべての人の想像力にとって主観的なものです。ほとんどの場合、asyncとawaitが仕事をしますが、メインスレッドをブロックしないので、アプリケーションは「待つ」まで応答し続けます。結果。ご意見ありがとうございます。次回はもっとうまくいくことを願っています:) - Dan
  • OPsの質問は、なぜそのような理由があるのかということを本当に質問していると思います。" FooAsync()を待つときに呼び出すメソッドが実際に非同期に実行されるという保証はありません。 OPは「重い数学」を使用して良い例を示していないかもしれませんが、本当です。シナリオ - Brian Ogden

6

非同期は並列を意味しません

非同期は並行性のみを意味します。実際、明示的なスレッドを使用しても、それらが同時に実行されることを保証するものではありません(たとえば、スレッドが同じシングルコアに親和性がある場合、またはより一般的には最初のマシンにコアが1つしかない場合)。

したがって、非同期操作が他のものと同時に発生するとは思わないでください。非同期はそれが起こることを意味します別の時にある(ギリシャ語)=なし、シン(ギリシャ語)=一緒に、クロノス(ギリシャ語)=時間=>非同期=同時に起こっていない)。

注:非同期性の概念は、呼び出し時にコードが実際に実行されるタイミングを気にしないということです。これにより、可能であれば、システムは並列処理を利用して操作を実行できます。それはすぐに走るかもしれません。それは同じスレッド上でさえ起こり得ます…それについては後でもっと詳しく説明します。

あなたがawait非同期操作は、あなたが作成している並行性(ラテン語)=一緒に、ラン(ラテン)=実行=> "同時" =一緒に走る)これは、先に進む前に非同期操作が完了するように要求しているためです。実行は収束したと言えます。これは、スレッドを結合するという概念に似ています。


非同期をパラレルにできない場合

async / awaitを使用するとき、FooAsync()を待つときに呼び出すメソッドが実際に非同期的に実行されるという保証はありません。内部実装は完全同期パスを使用して自由に戻ることができます。

これは3つの方法で起こる可能性があります。

  1. 使用できますawait返されるものすべてにTask。あなたが受け取るときTaskそれはすでに完成しているかもしれません。

    それでも、それだけで同期的に実行されたわけではありません。実際、それは非同期的に実行され、あなたがTaskインスタンス。

    できることを覚えておいてくださいawait既に完了したタスクについて

    private static async Task CallFooAsync()
    {
        await FooAsync();
    }
    
    private static Task FooAsync()
    {
        return Task.CompletedTask;
    }
    
    private static void Main()
    {
        CallFooAsync().Wait();
    }
    

    また、asyncメソッドはありませんawait同期的に実行されます。

    注:すでにご存知のように、Taskネットワークやファイルシステムなどで待機している可能性があります。Threadまたはで何かをエンキューするThreadPool

  2. シングルスレッドによって処理される同期コンテキストでは、結果は次のようになります。Task同期的に、いくらかのオーバーヘッドを伴います。これはUIスレッドの場合です、私は以下で何が起こるかについてもっと話します。

  3. カスタムを書くことは可能ですTaskScheduler常にタスクを同期的に実行するため。同じスレッドで、それは呼び出しを行います。

    注:最近私はカスタムを書いたSyncrhonizationContextそれはシングルスレッド上でタスクを実行します。あなたはそれを見つけることができます(System.Threading.Tasks。)タスクスケジューラを作成する。それはそのようになるだろうTaskSchedulerへの呼び出しでFromCurrentSynchronizationContext

    デフォルトTaskSchedulerへの呼び出しをエンキューします。ThreadPool。まだあなたが操作を待っているとき、それが上で実行されていない場合ThreadPoolそれはからそれを削除しようとしますThreadPoolそしてそれをインラインで実行します(待機しているのと同じスレッドで...スレッドはとにかく待機しているのでビジーではありません)。

    注:注目すべき例外の1つはTaskでマークLongRunningLongRunning Tasksは別のスレッドで実行されます


あなたの質問

私がCPUに縛られている長時間実行されているタスクを持っているなら(それがたくさんの難しい数学をしているとしよう)、それからそのタスクを非同期的に実行することはいくつかのスレッドを正しくブロックしなければなりませんか?何かが実際に数学をしなければなりません。私がそれを待っているなら、それからいくつかのスレッドはブロックされています。

あなたが計算をしているなら、それらはあるスレッドで起こらなければなりません、その部分は正しいです。

それでも、の美しさasyncそしてawait待機しているスレッドをブロックする必要がないということです(後で詳しく説明します)。それでも、待機しているタスクを待機しているのと同じスレッドで実行するようにスケジュールし、同期実行を行うことで非常に簡単に自分自身を撃つことができます(これはUIスレッドでは簡単な間違いです)。

の主な特徴の1つasyncそしてawait彼らはSynchronizationContext呼び出し元から。デフォルトを使用することになるほとんどのスレッドでTaskScheduler(前述のように、ThreasPool)ただし、UIスレッドの場合、タスクをメッセージキューにポストすることを意味します。つまり、タスクはUIスレッド上で実行されます。これの利点はあなたが使用する必要がないということです。InvokeまたはBeginInvokeUIコンポーネントにアクセスします。

私がする方法に入る前にawaitあるTaskブロックせずにUIスレッドから、私はそれを実装することが可能であることに注意したいですTaskSchedulerどこならawaitTask自分のスレッドをブロックしたりアイドル状態にしたりしないでください。代わりに、自分のスレッドに別のスレッドを選択させます。Taskそれは実行を待っています。私が...だったとき.NET 2.0のバックポートタスク私はこれを試しました。

真に非同期的なメソッドの例は何ですか?また、それらは実際にどのように機能しますか?ハードウェア機能を利用してスレッドがブロックされることのないI / O操作に限定されていますか。

あなたは混乱しているようです非同期スレッドをブロックしていない。あなたが欲しいものがスレッドをブロックすることを必要としない.NETの非同期操作の例であるならば、あなたが握りやすいと思うかもしれないそれをする方法は使用することです続きの代わりにawait。そして、あなたがUIスレッド上で実行する必要がある継続のために、あなたは使うことができますTaskScheduler.FromCurrentSynchronizationContext

派手なスピン待ちを実装しない。そしてそれによって私はTimerApplication.Idleそれとも何か。

使うときasyncあなたはそれを破ることができるような方法でメソッドのコードを書き直すようにコンパイラに言っています。結果は継続と似ていますが、構文がはるかに便利です。スレッドがawaitTaskスケジュールされます、そしてスレッドは現在の後に自由に続けることができます。async呼び出し(メソッド外)ときにTaskされている(続きの後)await) 計画されている。

UIスレッドの場合、これは一度到達したことを意味します。awaitメッセージを処理し続けることは自由です。一度待ったTaskされている(続きの後)await)予定されます。その結果、awaitスレッドをブロックするわけではありません。

まだ盲目的に追加asyncそしてawaitあなたの問題をすべて解決するわけではありません。

私はあなたに実験を提出します。新しいWindowsフォームアプリケーションを入手するButtonそしてTextBox次のコードを追加します。

    private async void button1_Click(object sender, EventArgs e)
    {
        await WorkAsync(5000);
        textBox1.Text = @"DONE";
    }

    private async Task WorkAsync(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

それはUIをブロックします。何が起こるかというと、先に述べたように、await自動的にSynchronizationContext呼び出し元スレッドのこの場合、それがUIスレッドです。したがって、WorkAsyncUIスレッド上で実行されます。

これが起こるのです:

  • UIスレッドはクリックメッセージを取得し、クリックイベントハンドラを呼び出します。
  • クリックイベントハンドラでは、UIスレッドがawait WorkAsync(5000)
  • WorkAsync(5000)(そしてその継続をスケジュールする)現在の同期コンテキストで実行するようにスケジュールされます。それはUIスレッド同期コンテキストです…それはそれを実行するためにメッセージをポストすることを意味します
  • UIスレッドは、それ以降のメッセージを自由に処理できるようになりました
  • UIスレッドは実行するメッセージを選択しますWorkAsync(5000)そしてその継続をスケジュール
  • UIスレッドが呼び出すWorkAsync(5000)継続して
  • WorkAsync、UIスレッドが実行されます。Thread.Sleep。 UIは5秒間反応しなくなりました。
  • 継続は、クリックイベントハンドラの残りを実行するようにスケジュールします。これは、UIスレッドに別のメッセージを投稿することによって行われます。
  • UIスレッドは、それ以降のメッセージを自由に処理できるようになりました
  • UIスレッドは、clickイベントハンドラで続行するメッセージを選択します
  • UIスレッドはテキストボックスを更新します

その結果、オーバーヘッドのある同期実行になります。

はい、あなたは使うべきですTask.Delay代わりに。それはポイントではありません。考えてSleepいくつかの計算を代用します。ポイントはただ使うだけですasyncそしてawaitどこでもあなたに自動的に並列であるアプリケーションを与えることはありません。バックグラウンドスレッドで何を実行したいのかを選択することをお勧めします。ThreadPoolそして、あなたは何をUIスレッドで実行したいですか。

それでは、次のコードを試してください。

    private async void button1_Click(object sender, EventArgs e)
    {
        await Task.Run(() => Work(5000));
        textBox1.Text = @"DONE";
    }

    private void Work(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

待ってもUIがブロックされないことがわかります。これは、この場合ですThread.Sleep今で実行されていますThreadPoolおかげでTask.Run。そしてありがとうbutton1_Clickであることasyncコードが到達したらawaitUIスレッドは自由に機能し続けます。後にTask完了すると、コードはawaitそれを可能にするためにコンパイラがメソッドを書き換えたおかげで。

これが起こるのです:

  • UIスレッドはクリックメッセージを取得し、クリックイベントハンドラを呼び出します。
  • クリックイベントハンドラでは、UIスレッドがawait Task.Run(() => Work(5000))
  • Task.Run(() => Work(5000))(そしてその継続をスケジュールする)現在の同期コンテキストで実行するようにスケジュールされます。それはUIスレッド同期コンテキストです…それはそれを実行するためにメッセージをポストすることを意味します
  • UIスレッドは、それ以降のメッセージを自由に処理できるようになりました
  • UIスレッドは実行するメッセージを選択しますTask.Run(() => Work(5000))そして終了したら継続を予定しなさい
  • UIスレッドが呼び出すTask.Run(() => Work(5000))継続して、これは上で実行されますThreadPool
  • UIスレッドは、それ以降のメッセージを自由に処理できるようになりました

ときにThreadPool完了すると、継続によって、クリックイベントハンドラの残りの部分が実行されるようにスケジュールされます。これは、UIスレッドに別のメッセージを投稿することによって行われます。 UIスレッドがclickイベントハンドラで続行するメッセージを選択すると、テキストボックスが更新されます。


  • 「a」はギリシャ語です。なしで。ラテン語ではありません。 - Thanasis Ioannidis
  • @ThanasisIoannidisが修正されました - Theraot

4

スティーブンの答えはすでに素晴らしいので、私は彼が言ったことを繰り返すつもりはない。私はスタックオーバーフロー(そして他の場所で)で同じ議論を何度も繰り返すことについて私の公正な分担をしました。

代わりに、非同期コードに関する1つの重要な抽象的事項に焦点を当ててみましょう。これは絶対修飾子ではありません。コードの一部が非同期であると言っても意味がありません - それは常に非同期です他の何かに関して。これは非常に重要です。

の目的await非同期操作といくつかの関連する同期コードの上に同期ワークフローを構築することです。あなたのコード現れる完全に同期1コード自体に。

var a = await A();
await B(a);

イベントの順序は次のように指定されます。await呼び出しBはAの戻り値を使用します。つまり、AはBの前に実行されている必要があります。このコードを含むメソッドは同期ワークフローを持ち、2つのメソッドAとBは互いに同期しています。

同期ワークフローは通常、より簡単に考えることができ、さらに重要なことに、多くのワークフローは単純に考えることができるので、これは非常に便利です。あります同期的です。実行するためにBがAの結果を必要とする場合、しなければならないAの後に実行2。別のHTTPリクエストのURLを取得するためにHTTPリクエストを行う必要がある場合は、最初のリクエストが完了するのを待つ必要があります。スレッド/タスクスケジューリングとは関係ありません。注文する必要のないものに注文を強制する「偶発的な同期」とは別に、おそらくこの「固有の同期」と呼ぶことができます。

あなたは言う:

私の考えでは、私は主にUI開発を行っているので、非同期コードはUIスレッド上では実行されず、他のスレッド上では実行されないコードです。

あなたは、UIに関して非同期的に実行されるコードを記述しています。これは確かに非同期にとって非常に便利なケースです(人々は応答を停止するUIが好きではありません)。しかし、それはより一般的な原則の単なる特定のケースです - 物事が互いに関して順不同で起こることを可能にします。これも絶対ではありません一部順番がずれているイベント(ユーザーがウィンドウをドラッグしたとき、またはプログレスバーが変更されたときに、ウィンドウを再描画する必要がある場合など)ではない順番が違っている(Loadアクションが終了する前にProcessボタンをクリックしてはいけない)。awaitこのユースケースではそうではありませんそれ使用とは異なりますApplication.DoEvents原則として - それは同じ問題と利点の多くを紹介します。

これはまた、元の引用が面白くなる部分です。 UIを更新するにはスレッドが必要です。そのスレッドはイベントハンドラを呼び出します。await。それはその行があるという意味ですかawaitユーザー入力に応じてUIが自分自身を更新できるようにするために使用されるものいいえ

まず、それを理解する必要がありますawaitそれがメソッド呼び出しであるかのように、その引数を使用します。私のサンプルでは、Aによって生成されたコードの前にすでに呼び出されている必要があります。await「UIループに制御を戻す」など、何でもできます。の戻り値AですTask<T>ただの代わりにT「将来の可能性のある価値」を表す - そしてそしてawait生成されたコードは、値がすでに存在するか(その場合は同じスレッドで継続するか)どうかを確認します(つまり、スレッドをUIループに解放します)。しかしどちらの場合も、Task<T>価値そのものしなければならないから返されましたA

この実装を検討してください。

public async Task<int> A()
{
  Thread.Sleep(1000);

  return 42;
}

呼び出し側のニーズA値を返す(intのタスク)。ないからawaitメソッド内のs、つまりreturn 42;。しかし、2つの操作はスレッドに関して同期的であるため、スリープが終了する前にそれを行うことはできません。それが使用するかどうかに関係なく、呼び出し元のスレッドは1秒間ブロックされます。awaitかどうか - ブロッキングが行われているA()それ自体ではなくawait theTaskResultOfA

対照的に、これを考慮してください。

public async Task<int> A()
{
  await Task.Delay(1000);

  return 42;
}

実行がすぐにawait待機中のタスクがまだ終了していないことがわかり、呼び出し元に制御が戻ります。そしてそのawaitその結果、呼び出し側では制御をその呼び出し元私たちはUIに関していくつかのコードを非同期にすることに成功しました。 UIスレッドとAの間の同時性は偶然のもので、私たちはそれを取り除きました。

ここで重要なのは、コードを調べずに2つの実装を外部から区別する方法がないということです。戻り値の型だけがメソッドシグネチャの一部です - メソッドが非同期的に実行されるとは言っていません。よろしく。これにはいくつかの正当な理由があるので、それを戦う意味はありません - 例えば、結果がすでに利用可能であるときに実行のスレッドを壊す意味がありません。

var responseTask = GetAsync("http://www.google.com");

// Do some CPU intensive task
ComputeAllTheFuzz();

response = await responseTask;

我々はいくつかの仕事をする必要があります。一部のイベントは他のイベントに対して非同期的に実行できます(この場合、ComputeAllTheFuzzHTTP要求とは無関係で非同期です。しかし、ある時点で、同期ワークフローに戻る必要があります(たとえば、次の両方の結果を必要とするもの)。ComputeAllTheFuzzそしてHTTPリクエスト)。それはawait実行を再度同期させるポイント(複数の非同期ワークフローがある場合は、次のようにします。Task.WhenAll)ただし、HTTP要求が計算の前に完了した場合、その時点で制御を解放しても意味がありません。await要点 - 同じスレッドで単純に続行できます。 CPUを無駄にすることはありません - スレッドをブロックすることはありません。それは便利なCPUの仕事をします。しかし、UIが更新される機会はありませんでした。

これはもちろん、このパターンがより一般的な非同期メソッドで通常避けられる理由です。非同期コードの用途によっては(スレッドやCPU時間の浪費を避けるために)役立ちますが、そうでない場合(UIの応答性を保つため)は役に立ちます。そのような方法でUIの反応を良くすることを期待しているのであれば、結果に満足できないでしょう。しかし、たとえばWebサービスの一部として使用すると、うまく機能します。スレッドを無駄にすることを避け、UIの応答性を維持することではありません(既にサービスエンドポイントを非同期で呼び出すことによって提供されます)。サービス側でも同じことが言えます。

要するに、await呼び出し側に関して非同期のコードを書くことができます。それは、魔法のような非同期性を生み出すのではなく、すべてに関して非同期ではなく、CPUの使用やスレッドのブロックを妨げることもありません。非同期操作から同期ワークフローを簡単に作成し、呼び出し元に対してワークフロー全体の一部を非同期として表示するためのツールを提供するだけです。

UIイベントハンドラを考えてみましょう。個々の非同期操作が実行するためにスレッドを必要としない場合(例えば非同期I / O)、非同期方法の一部は他のコードが元のスレッド上で実行することを可能にする(そしてUIはそれらの部分で応答し続ける)。操作が再びCPU /スレッドを必要とするとき、それは必要とするかもしれませんしないかもしれません。元の作業を継続するためのスレッド。もしそうであれば、CPU作業の間UIは再びブロックされます。そうでなければ(ウェイターこれを使ってこれを指定するConfigureAwait(false))、UIコードは並行して実行されます。もちろん両方を処理するのに十分なリソースがあると仮定します。 UIを常に反応させ続ける必要がある場合、目立つほど長く実行するためにUIスレッドを使用することはできません。たとえ信頼できない「通常は非同期ですが、場合によっては数秒間ブロックする」非同期をラップする必要があるとしてもです。メソッドTask.Run。両方のアプローチにはコストと利点があります - すべてのエンジニアリングと同様に、これはトレードオフです。


  1. もちろん、抽象化の範囲内では完璧です - すべての抽象化がリークしているので、非同期実行のためのリークやその他のアプローチがたくさんあります。
  2. 十分にスマートなオプティマイザは、Aの戻り値が実際に必要とされるところまで、Bのある部分を実行することを許可するかもしれません。これはあなたのCPUが通常の "同期"コードで行うことです(アウトオブオーダー実行)そのような最適化しなければならないただし、CPUが操作の順序を誤って判断した場合は、結果を破棄して正しい順序を提示する必要があります。


3

これは、非同期/待機によって、コードが制御をブロックして別のフローに制御を解放し、その後制御を再開するがスレッドを必要としないことを示す非同期コードです。

public static async Task<string> Foo()
{
    Console.WriteLine("In Foo");
    await Task.Yield();
    Console.WriteLine("I'm Back");
    return "Foo";
}


static void Main(string[] args)
{
    var t = new Task(async () =>
    {
        Console.WriteLine("Start");
        var f = Foo();
        Console.WriteLine("After Foo");        
        var r = await f;
        Console.WriteLine(r);
    });
    t.RunSynchronously();
    Console.ReadLine();
}

enter image description here

ですから、async / awaitの鍵となる結果が欲しいときにコントロールを解放して再同期するということです。

注:このコードの作成でスレッドはブロックされませんでした:)

私は時々混乱が何かがそれ自身のスレッドで実行されていることを意味しない「タスク」から来るかもしれないと思います。それはただすることを意味するだけで、非同期/待機はタスクをステージに分割し、それらのさまざまなステージをフローに調整することを可能にします。

それは料理のようなものです、あなたはレシピに従います。あなたは料理のために皿を組み立てる前にすべての準備作業をする必要があります。それでオーブンの電源を入れ、物を切ったり、物をかき回したりします。それから、オーブンの温度を待って準備作業を待ちます。論理的に思える方法でタスク間を自分で交換することでそれを行うことができます(tasks / async / await)が、物事を早くするためにニンジン(スレッド)を切り刻んでいる間、他の誰かにチーズのおろしを手伝ってもらうことができます。

リンクされた質問


関連する質問

最近の質問