Skip to content

C#(Xamarin.Mac)で非同期処理を扱う練習用プロジェクトです。

Notifications You must be signed in to change notification settings

DogFortune/HelloAsyncAwait

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

非同期処理をイメージする為のプロジェクト

C#(Xamarin.Mac)で非同期処理を行う際の解説として作成したソリューションです。

解説

時間のかかる処理を普通に実行するとどうなるか?

ソリューションをビルドして実行します。ボタンとプログレスバーとラベルがある簡単なGUIアプリが起動します。 ここでボタンを押すとHeavyMethod()(時間のかかる処理)が実行されるという流れです。今回はプログレスバーを待機時間を入れつつ動かすという処理にしています。 押すとUIが固まってしまいます。これが時間のかかる処理を同期的に行った時の弊害です。 処理が終わるまで結果をアプリが待ち続ける事になるので固まってしまうというわけです。 ここで出てくるのが「非同期処理」です。

非同期にしてみる

それでは重い処理を非同期処理に切り出してみましょう。HeavyMethod()をタスクに切り出します。

Label.StringValue = "開始";
Task.Run(() => HeavyMethod());
Label.StringValue = "完了";

HeavyMethod()をタスクとして切り出し、別スレッドで処理を行うようにします。 こうすれば重い処理は別スレッドで処理されるので、UIが固まるのを防げます。 では実行してみましょう。どうなりましたか? ラベルが完了ってなってからプログレスバーが伸び始めます。これではおかしいですね?

  1. ラベルが「開始」になる
  2. 重い処理の実行開始。その間プログレスバーが伸びる
  3. 重い処理が完了
  4. ラベルが「完了」になる

これが望んでいる処理ですがそうなってくれません。なぜでしょう? それは非同期処理の結果を待機していないからです。 あくまで上記の記述は、重い処理をタスクとして切り出しただけです。Taskはタスクであり、非同期処理そのものではありません。 タスクとして切り出せば他の処理が実行できるので、コード的には次の行に進む事ができます。 これがラベルがすぐに「完了」になってしまった原因です。 ではどうするか? 簡単です。タスクが完了するまで待機してもらうようにすればいいのです。 そのための語句がawaitです。

Label.StringValue = "開始";
await Task.Run(() => HeavyMethod());
Label.StringValue = "完了";

こうするとタスクの完了を待機してくれるようになるので、ラベルがすぐに「完了」とならなくなります。 実際に待機すると処理が止まってしまうので、他の処理をしつつ定期的にタスクを実行しているスレッドの状況を見て、 終わっているかどうかを見に来るみたいなイメージですね。終わっていなければ引き続き待機しておき、終わっていたら次の行を実行するみたいな感じです。

しかしawaitを足すとエラーになります。 await演算子は非同期メソッドでのみ使用できます。メソッドにasync演算子を指定し、戻り値をTaskにする事を検討して下さい。 と出ます。何?って感じですね。

async/await

先程行ったように、awaitをつけるとタスクの完了を待機するようになります。 なので待機している結果を受け取る仕組みが必要です。 その為のルールとして、C#ではメソッドにasyncをつける必要があるのです。 また待機した結果を受け取る為に、戻り値をvoidではなくTaskにするのです。 ちなみにasync voidでも書けるのですが、これだとawaitで待機している非同期処理の結果を受け取る事ができません。 また非同期処理の中で例外が発生した場合、例外を見つける事ができなくなります。なので原則使うことはありません。 唯一の例外は、UIから直接実行されるイベントです。ここだけasync voidが許されます。

では書き換えて実行してみましょう。上手くいくはずです。これがTaskを用いた非同期処理の仕組みです。

async voidの弊害

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

About

C#(Xamarin.Mac)で非同期処理を扱う練習用プロジェクトです。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages