23

私は新しい非同期について読んでいますawaitこれは素晴らしいキーワードですが、これまで見てきたイントロビデオで答えを見つけることができなかった重要な質問が1つあります(しばらく前にホワイトペーパーを読んでいます)。

に電話があるとします。awaitメインUIスレッドの入れ子関数この時点でスレッドはどうなりますか?制御はメッセージループに戻り、UIスレッドは他の入力を自由に処理できますか?

待機中のタスクが完了すると、スタック全体がメッセージキューにプッシュされ、制御がそれらのネストされた各関数を介して返されるようになりますか。それとも、ここで何か他のことが起こっていますか。

第二に(私があなたの注意を払っている間)、なぜ非同期メソッドがでラベル付けされる必要があるのか、私は本当にわかりませんasync。どのメソッドも非同期に実行できませんか?メソッドを非同期的に実行したいのにasyncキーワードがない場合はどうなりますか?それを簡単に行う方法はありますか?

乾杯。 :)

編集する確かに、サンプルコードをコンパイルすることができれば、おそらく自分でそれを理解することしかできませんでしたが、何らかの理由でそこにブロックがあります。私が本当に知りたいのは、継続がどの程度継続するかです。それは、タスクが完了したときに再開するために、コールスタック全体をフリーズしますか、それともそれだけで戻りますか?継続をサポートするために関数自体を非同期としてマークする必要がありますか?それとも(私が最初に尋ねたように)それは呼び出しスタック全体を継続しますか?

それであればしない呼び出しスタック全体をフリーズします。非同期待ちが非同期呼び出し関数にぶつかるとどうなりますか?それはそこでブロックしますか?それは待ち望んでいた点を破ったのではないでしょうか。私はこれを学び続けることができるように誰かが記入することができることを私がここに理解を欠いていることをあなたが見ることができることを願っています。


  • Eric Lippertには、ブログ記事待機/非同期のしくみについて。これはあなたの質問すべてに答えるはずです。 - dtb
  • Jon Skeetにはまた、投稿彼が非同期CTPを調べて、基本的にそれを再実装するところ。彼はまた、いくつかのシナリオでコンパイラが生成したコードも示しています。 - bbogovich
  • どちらのリンクも無効になりました、Eric Lippertのブログ投稿は現在ここにそしてEduasyncは今ここに - dlatikay

2 답변


26

メインのUIスレッドで入れ子になった関数で待つための呼び出しがあるとします。この時点でスレッドはどうなりますか?制御はメッセージループに戻り、UIスレッドは他の入力を自由に処理できますか?

はい。あなたがawait(aなど)Task<TResult>)内のスレッドの現在位置asyncメソッドがキャプチャされます。次に、それは、待機可能物が終了したときに実行されるように、方法の残りの部分(「継続」)を待ち行列に入れる。Task<TResult>完了します)。

ただし、最適化が行われることがあります。awaitableが既に終了している場合は、await待つ必要はありません、そしてそれはただただメソッドを実行し続けます。これは「高速パス」と呼ばれます。ここに記述

待機中のタスクが完了すると、スタック全体がメッセージキューにプッシュされて、制御がそれらのネストされた各関数を介して返されるようになりますか。

スレッドの現在位置はUIメッセージキューにプッシュされます。詳細はもう少し複雑です。継続が予定されていますTaskScheduler.FromCurrentSynchronizationContextない限りSynchronizationContext.Currentですnullその場合それらは予定されていますTaskScheduler.Current。また、この振る舞いは以下を呼び出すことによって上書きすることができます。ConfigureAwait(false)これは常にスレッドプールでの継続をスケジュールします。からSynchronizationContext.CurrentUIですSynchronizationContextWPF / WinForms / Silverlightの場合、この継続はUIメッセージキューにプッシュされます。

第二に(私があなたの注意を払っている間に)、なぜ非同期メソッドがasyncでラベル付けされる必要があるのか、私は本当に理解していません。どのメソッドも非同期に実行できませんか?メソッドを非同期的に実行したいのにasyncキーワードがない場合はどうなりますか?それを簡単に行う方法はありますか?

これらは「非同期」という言葉の意味が少し異なります。のasyncキーワードでawaitキーワード。言い換えると、async方法はawait。昔ながらの非同期デリゲート(つまり、BeginInvoke/EndInvoke)とはかなり異なりますasync。非同期デリゲートは、ThreadPoolスレッド、しかしasyncメソッドはUIスレッド上で実行されます(それらがUIコンテキストから呼び出され、あなたが呼び出さないと仮定します)。ConfigureAwait(false)

あなたが(非 - async)メソッドはThreadPoolスレッド、あなたはこのようにそれを行うことができます:

await Task.Run(() => MyMethod(..));

私が本当に知りたいのは、継続がどの程度継続するかです。それは、タスクが完了したときに再開するために、コールスタック全体をフリーズしますか、それともそれだけで戻りますか?継続をサポートするために関数自体を非同期としてマークする必要がありますか?それとも(私が最初に尋ねたように)それは呼び出しスタック全体を継続しますか?

現在位置が取得され、継続が実行されると「再開」します。使用する機能await継続を支援するためには印をつけなければならないasync

あなたが電話している場合async非からのメソッドasync方法、それから対処しなければなりませんTask直接オブジェクト。これは通常行われていません。トップレベルasyncメソッドが返す可能性がありますvoidだから、持っていない理由はありませんasyncイベントハンドラ

ご了承くださいasync純粋にコンパイラ変換です。それはそれを意味しますasyncメソッドは、コンパイル後の通常のメソッドとまったく同じです。 .NETランタイムはそれらを特別な方法で処理しません。


  • 私は、コールスタックについては真実だとは思わない。 await式によって確実にイールドが発生した後に例外をスローした場合、その例外は元の呼び出しスタックにあったキャッチブロックによっては捕捉されません。 - Damien_The_Unbeliever
  • いいえ。私はちょうど100%確実であるためにテストを実行しました。例外はうまく捕らえられた。例外はで囲まれていることに注意してください。Taskだから、あなたはする必要がありますawait例外を見るためです。しかし、それからそれはちょうどコールスタックを上に移動します。 - Stephen Cleary
  • コールスタック内のすべての(またはほとんどの)発信者が非同期を使用するように変換された状況について話していると思います。それがOPの頭の中のイメージかどうか、よくわかりません。私は非同期メソッドを呼び出してタスクを返す非同期コードのスタックを描いていました。そのタスクが返されると(そして非同期メソッドが完了する前に)、スタック内の非同期ではないメソッドはすべて終了します。 - Damien_The_Unbeliever
  • あなたの言っていることがわかります。その場合、例外はTask後で観察されるオブジェクト。フルスタックがキャプチャされたとしても(この場合はそうではありません)、catch例外がに取り込まれたため、ブロックは実行されませんTask。 - Stephen Cleary

4

それはAwaitableの振る舞いによります。

それは同期的に実行する、すなわちスレッド上で実行し、同じスレッド上の待機者に制御を戻すという選択肢を有する。

非同期に実行することを選択した場合、待機者は、待機者がコールバックをスケジュールしているスレッドでコールバックされます。その間、呼び出し側スレッドは解放されます。待機中の開始があるので、非同期作業で終了し、待機者はその待機に待機者のコールバックをアタッチしています。

2番目の質問に関しては、asyncキーワードは、メソッドが非同期に呼び出されるかどうかではなく、そのメソッドの本体が非同期コードをそれ自体インラインで呼び出したいかどうかということです。

すなわちTaskまたはTaskを返す任意のメソッドは非同期的に(待機またはcontinuewithで)呼び出すことができますが、asyncでマークすることで、そのメソッドは本文でawaitキーワードを使用できるようになります。 Tは、ボディ全体がタスクが実行するステートマシンに書き換えられるためです。

方法があるとしましょう

public async Task DoSomething() {
}

あなたがUIスレッドからそれを呼び出すとき、あなたはタスクを取り戻します。この時点で、タスクをブロックすることができます。.Wait()または.ResultそのTaskSchedulerでasyncメソッドを実行するか、でブロックします。.RunSynchronously()これはUIスレッド上で実行されます。もちろん内部で起こるどんな待ちもDoSomething基本的には別の継続です。そのため、TaskSchedulerスレッドでコードの一部を実行してしまう可能性があります。しかし、最終的にはUIスレッドは完了するまでブロックされます。これは通常の同期メソッドとまったく同じです。

または、で継続をスケジュールすることができます.ContinueWith()タスクが完了したときにTaskSchedulerによって呼び出されるアクションを作成します。これはすぐに現在のコードに制御を戻します。現在のコードは、UIスレッドで実行していたことをすべて実行し続けます。継続はコールスタックをとらえるのではなく、単なるActionなので、外部スコープからアクセスするすべての変数を単にとらえるだけです。


  • 私が特に知りたいのは全体callstackは継続として設定され、UIスレッドは実際には終了できないため、これがUIスレッドでどのように機能するのか。 - devios1
  • 非同期メソッドは、コンパイラによってステートマシンに書き換えられます。メソッドが「中断」した場合は、待って、基本的にステートマシンはスコープ内のすべての変数の現在の状態をキャプチャしました。そのため、コールスタックには何もしません。 - Arne Claassen
  • UIスレッドについて:非同期メソッドを呼び出すと(つまり、待つことができる唯一の場所を使用できるようになると)、タスクが返されます。そのため、UIスレッドでそれを行う場合は、タスクをブロックする(UIスレッドをブロックする)か、またはUIスレッドを続行できるContinueWithを追加します - Arne Claassen

リンクされた質問


関連する質問

最近の質問