awaitの罠?
手元に開発環境がないのでうろ覚えではあるが・・・
awaitの後のコードが実行されないというケースがあった。
Debug.WriteLine("hoge1"); await hoge2(); Debug.WriteLine("hoge3");
というコードで、hoge2は確かに最後まで実行されているのになぜかhoge3が出力されない。
Debug.WriteLine("hoge1"); hoge2().Wait(); Debug.WriteLine("hoge3");
だとhoge3が出力される。
タイミングとしては、アプリ終了時のOnSuspendingから呼び出された時に起きるようだ。
その他のコードから呼び出すとawait使用時でもhoge3は出力される。
えてしてマルチスレッドというものは、フォアグラウンドスレッドが終わるとバックグラウンドスレッドは通知も何もなくいきなり強制終了されたりするもんだけど、もしかしてこれもそのせい?
以前どこかのサイトでawaitの前後は実行スレッドが変わるというのを読んだことがある。
awaitの後のコードはコンパイラによって別のTaskで実行されるようになるんだとか何とか。
であればhoge2を実行するスレッドがなんであれ、hoge3を出力するコードはバックグラウンドスレッドで実行されることになる。
通常の中断状態ではまた違うのだろうが、ユーザーがアプリを明示的に終了させた場合は中断状態から即終了させられる。
おそらくOnSuspending終了後にはOSによってすべてのスレッドが終了させられてしまうんだろう。
OnSuspending自体はフォアグラウンドスレッドで実行されていて、(ある程度の時間が過ぎるまでは)強制終了されることはない。
しかし、その中でバックグラウンドスレッドが起動されるようなことがあると、そのスレッドの状態がどうであろうとOnSuspendingを抜ければプロセスごと終了させられてしまう。
hoge3が出力されないのはそういう理屈なのではなかろうか?
UIスレッドが実行しているコードであれば、UIに関係するコードはUIスレッドでしか実行できないから、awaitを使った場合でもバックグラウンドにはならないような作りになっているそうだが、OnSuspendingはUIスレッドで実行されてるのかな?
だとしても問題のコードはOnSuspendingからいくつかのawait付き呼び出しやらイベント呼び出しを経由しているし、そもそも通常の動作時でも頻繁に呼び出すコードなのでUIスレッドで実行するわけにもいかないような・・・
awaitではなくWait()で待機すれば問題ないような気もするが、awaitは他でもたくさん使っていて、変にawaitとWait()を混在させるとどこかでデッドロックしてしまうのを度々経験してるもんで、あまり使いたくない。今動いてるのはたまたまだという気がしてならない。
そもそも中断時にawaitを使うような時間のかかることをするなって考えもあるが、中断時に必要とされる処理はアプリが使ったリソースの後始末とアプリの状態の保存である。
windows8の場合、リソース関係の処理はフレームワークからして(ほぼ?)すべてawait/asyncで使うようになっているんだからどうしようもない。
時間がかかるからawaitを使ってるのではなく、フレームワークに非同期メソッドしかないから使わざるを得ない。
長々と書いたが、要約すると
・OnSuspendingから(直接間接問わず)await付きメソッドを呼ぶと問題があるかも知れない
・根本的対策:なし
・暫定対策:awaitではなくWait()で待機するようにして、デッドロックが起きないことを祈る
ってこと。
2013/01/10補足記事追加。
イベントハンドラのasync - たっくてっくのーと
2013/01/31さらに補足というか勘違いの修正。
asyncとawaitとtaskとthread - たっくてっくのーと