■91660 |
Re[5]: async/awaitの使い方 |
□投稿者/ 魔界の仮面弁士 -(2019/07/18(Thu) 10:26:44)
| 2019/07/18(Thu) 10:55:18 編集(投稿者)
以下、自分の認識です。 おかしな所があったら突っ込み歓迎。
■No91657 (こまお さん) に返信 > 1) 私のコードで(1)をすると(2)へ来なくなる理由(デッドロック?)
No91647 のコードで言うと、フィールド変数 busy を書き換えている箇所は、 UI スレッド上で動作することになります。(Task 部は別スレッド)
つまり、「busy = false に戻す処理」と「Button_Click 内でのループ監視」が、 いずれも UI スレッドであることが問題になります。busy = false; に書き戻そうにも、 UI 側で、Button_Click がまだ終わっていない状態なので、処理を差し戻せません。
一つのスレッドで複数の処理を同時には実行することはできませんので、 busy = false に戻す処理は、Button_Click の処理完了まで待たされることになります。 しかし Button_Click は、ループ監視によって、busy = false; への変化を 待ち続けている状態なので、デッドロックします。
No91650 のコードでは、処理を差し戻す余地を与えるため、DispatcherFrame を利用しています。 WinForms でいうところの DoEvents のようなものです。 https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.threading.dispatcherframe
> 2) 上記に関係すると思いますが、仮面弁士さんの2番目の様にするとうまくいく理由
No91651 のコードで言うと、「busy = true;」までは UI スレッド。 「Task.Run」はワーカースレッド A。 「ContinueWith」がワーカースレッド B。
スレッド B は A の後に実行されるようになっていますが、 それぞれは UI スレッドとは独立しています。
busy = false; にする処理を、UI スレッドに行わせるのではなく、 ワーカースレッドで行う事になるので、 No91647 のように阻害されることがありません。
ただし、TaskScheduler.Current を TaskScheduler.FromCurrentSynchronizationContext() に 変更した場合は、ContinueWith 部が UI スレッドのコンテキストとなるため、 No91647 と同じ道を辿ることになります。 No91650 を併用しない限りは。
それぞれの処理部で Thread.CurrentThread.ManagedThreadId を Debug.WriteLine してみると、どのスレッドが呼ばれているかを 確認できるのではないでしょうか。
> 3) ContinueWith()に渡すAction<Task>の引数tとは何か
文字通りの Task です。
AsyncTest クラス内に private void Done(Task t) { busy = false; } を用意して、 await Task.Run(〜).ContinueWith(Done, TaskScheduler.Current); と書くのと同じことです。
なお、await 処理のところを Task taskObj = Task.Run(〜); await taskObj.ContinueWith(Done, TaskScheduler.Current); に書き換えた場合、Done の引数 Task t に渡されるインスタンスは、 上記の taskObj と同一の物となります。 |
|