C#(Xamarin.Mac)で非同期処理を行う際の解説として作成したソリューションです。
ソリューションをビルドして実行します。ボタンとプログレスバーとラベルがある簡単なGUIアプリが起動します。
ここでボタンを押すとHeavyMethod()(時間のかかる処理)
が実行されるという流れです。今回はプログレスバーを待機時間を入れつつ動かすという処理にしています。
押すとUIが固まってしまいます。これが時間のかかる処理を同期的に行った時の弊害です。
処理が終わるまで結果をアプリが待ち続ける事になるので固まってしまうというわけです。
ここで出てくるのが「非同期処理」です。
それでは重い処理を非同期処理に切り出してみましょう。HeavyMethod()
をタスクに切り出します。
Label.StringValue = "開始";
Task.Run(() => HeavyMethod());
Label.StringValue = "完了";
HeavyMethod()
をタスクとして切り出し、別スレッドで処理を行うようにします。
こうすれば重い処理は別スレッドで処理されるので、UIが固まるのを防げます。
では実行してみましょう。どうなりましたか?
ラベルが完了
ってなってからプログレスバーが伸び始めます。これではおかしいですね?
- ラベルが「開始」になる
- 重い処理の実行開始。その間プログレスバーが伸びる
- 重い処理が完了
- ラベルが「完了」になる
これが望んでいる処理ですがそうなってくれません。なぜでしょう?
それは非同期処理の結果を待機
していないからです。
あくまで上記の記述は、重い処理をタスクとして切り出しただけです。Taskはタスクであり、非同期処理そのものではありません。
タスクとして切り出せば他の処理が実行できるので、コード的には次の行に進む事ができます。
これがラベルがすぐに「完了」になってしまった原因です。
ではどうするか? 簡単です。タスクが完了するまで待機してもらうようにすればいいのです。
そのための語句がawait
です。
Label.StringValue = "開始";
await Task.Run(() => HeavyMethod());
Label.StringValue = "完了";
こうするとタスクの完了を待機してくれるようになるので、ラベルがすぐに「完了」とならなくなります。 実際に待機すると処理が止まってしまうので、他の処理をしつつ定期的にタスクを実行しているスレッドの状況を見て、 終わっているかどうかを見に来るみたいなイメージですね。終わっていなければ引き続き待機しておき、終わっていたら次の行を実行するみたいな感じです。
しかしawaitを足すとエラーになります。
await演算子は非同期メソッドでのみ使用できます。メソッドにasync演算子を指定し、戻り値をTaskにする事を検討して下さい。
と出ます。何?って感じですね。
先程行ったように、await
をつけるとタスクの完了を待機するようになります。
なので待機している結果を受け取る仕組みが必要です。
その為のルールとして、C#ではメソッドにasync
をつける必要があるのです。
また待機した結果を受け取る為に、戻り値をvoidではなくTask
にするのです。
ちなみにasync void
でも書けるのですが、これだとawaitで待機している非同期処理の結果を受け取る事ができません。
また非同期処理の中で例外が発生した場合、例外を見つける事ができなくなります。なので原則使うことはありません。
唯一の例外は、UIから直接実行されるイベントです。ここだけasync voidが許されます。
では書き換えて実行してみましょう。上手くいくはずです。これがTaskを用いた非同期処理の仕組みです。
AsyncVoidExceptions_CannotBeCaughtByCatch()
がサンプルです。
普通に呼び出した時と、ThrowExceptionAsync()
メソッドの戻り値をvoid
からTask
に書き換えた場合のスタックトレースを見比べてみましょう。
より詳細に出るだけではなく、Taskに切り替えた時にいろんな所にawaitを追加したり、その影響でメソッドにasyncをつけたと思います。
そうです。async voidだと呼び出し先が非同期かどうか判断できないので、async Taskで呼び出さなくても実行できてしまうのです。 呼び出し元に「このメソッドの中には非同期処理があるよ!」という事を伝えるために、メソッドにasyncをつける必要があるのです。 この仕組があることで、呼び出し元は安心して非同期処理を扱う事ができ、async Taskをつけ忘れる事がなく、例外が起こったとしても補足する事ができるのです。
async/awaitが難しい!
とありますが、そもそも非同期処理の仕組み自体が難しいので当然です。
しかしプログラミングを行う上ではほぼ必須と言ってもより考え方なので、ぜひ習得して下さい。
このプログラムが何かの役に立つ事を祈って。
このプログラムを使った事による一切の責任を負いません。 改変、再配布ご自由にどうぞ。 著作権表示も必要ありません。「使ったよ!」という連絡があると私が喜びます。
https://ufcpp.net/study/csharp/sp5_async.html https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming