rider.お品書きサクッと書いて公開して、後でちまちま修正する事がママあります。なんじゃこりゃ?と思ったら、数日してから見て貰えると嬉しいです。「スクラムの実践方法を分かりやすくマンガも交えて解説。ADは既に埋まっていた...)それには、こんな事情があったのです:「.NET非同期処理(async-await)を制御する、様々な方法」の記事がコンスタントにPVを稼いでいる(その割に、他の非同期関連の記事は読まれない)。asyncやawaitキーワードの使い方を度々聞かれる。Task.Run()とか、Task.Factory.StartNew()とか、Task.Start()とか使ってるコードを頻繁に見る(不要なのに)。Task.Wait()とか、Task.Resultとか使ってるコードを頻繁に見る(不要なのに)。その中でTask.Wait()とか(?!?!)私のセッションや記事は、「なぜなのか」を深掘りして問題の根本を理解してもらう事を念頭に置いているのですが、さすがにこれだけ頻出してるのは、業界的に非常に問題があるんじゃないだろうかと思いました。Coreが注目されると、今までC#でコード書いた事が無い、と言う人が、見よう見まねで書き始める時期じゃないかと思います(それで何となく書けてしまうのが、C#の間口の広いところではあるのですが)。必要性」のような記事を書いているのですが、まだ完成していないので、もうちょっとライトなやつを書いてさっさと公開した方が良い気がしてきました。「できる(なんちゃら)」じゃないけど、そういう資料も切り口を丁寧に詰めればありかも知れない...そういうわけで:C#の非同期処理(Taskとasync-await)を使ってコードを書くなら、以下の事を知っていれば9割がたの問題は回避できる!!ここで示した方法から外れたことをしようとしている場合は、殆ど間違いなく誤った記述をしようとしているので、もう一度何をしようとしているのか整理しよう!!(知っててやってる人はいいんです。こんな記事見る必要はありません)章立てを2章だけにして、極限まで絞り込みました。コードはやや冗長に書いています。同期処理と非同期処理を対比させてみる以下のコードは、ウェブサイトから「同期的に」データをダウンロードします。要するに、普通のコードです。ReadFromUrl(url);Console.WriteLine(body);}これを非同期処理として変形します。変形した箇所にはコメントを入れておきました。メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。メソッド内でawaitキーワードを使ったら、戻り値の型はTask<T>型で返さなければならない。T型は本来返すべき戻り値の型。非同期処理対応メソッドは、「慣例」で「~Async」と命名する(慣例なので必須ではない)。OpenReadから非同期対応のOpenReadTaskAsyncに変更する。OpenReadTaskAsyncがTask<Stream>を返すので、awaitする。awaitすると、Streamが得られる。ReadToEndから非同期対応のReadToEndAsyncに変更する。ReadToEndAsyncがTask<string>を返すので、awaitする。awaitすると、stringが得られる。メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。メソッド内でawaitキーワードを使ったら、戻り値の型はTask型で返さなければならない。ここでは元々返す値が無かった(void)なので、非ジェネリックなTask型を返す。非同期処理対応メソッドは、「慣例」で「~Async」と命名する(慣例なので必須ではない)。ReadFromUrlから非同期対応のReadFromUrlAsyncに変更する。ReadFromUrlAsyncがTask<string>を返すので、awaitする。awaitすると、stringが得られる。HttpClientではなくWebClientを使ったのは、対比させやすいためです。非同期前提で書くなら、始めからHttpClientを使うと良いでしょう。WebClientの代わりにHttpClientで同じ処理を書いてみるのは、良い練習になると思います。出来るだけわかりやすくなるようにコメントを入れたので、「ひたすらめんどくさそう」に見えますが、よく見れば大した作業ではない筈です。ReadFromUrlとDownloadのそれぞれのメソッドが、どのように変形されるのかを見てください。すると何となく法則が見えてくると思います。この作業で、2つのメソッド「ReadFromUrl」と「Download」が、両方とも非同期メソッド化された事にも注意してください。特別な理由がない限り、非同期処理は呼び出し元から末端まで、「全て非同期メソッドで実装する必要がある」、と言う事です。ここで示した変形を行うと、連鎖的に、すべてのメソッドが非同期化されます。以下にまとめます:非同期対応メソッドが存在する場合は、そのメソッドを呼び出すように切り替えます。上記では、「OpenRead」を「OpenReadTaskAsync」に変えました。目的の非同期メソッドは、「TaskやTask<T>を返すメソッドがあるかどうか」や「~Async」と命名されているメソッドがあるかどうか、を目印に探すと良いと思います。この理由のために、メソッド名を「~Async」と命名する慣例が生きてきます。TaskやTask<T>は、そのままでは戻り値T型になりません。これらの値は「await」キーワードを使う事で、戻り値を得る事が出来ます。awaitを実行している時、つまりそれが「非同期的に結果を待機している」と言う事です。自分で定義したメソッド内で「await」キーワードを使った場合は、メソッド先頭に「async」キーワードを追加する必要があります。以下のTask型云々は関係ありません。非同期メソッドの戻り値は、Task型、Task<T>型、または「void」とする必要がありますが、voidは殆ど使用しません(次章)。同期メソッドの戻り値の型がstringなら、Task<string>のように、ジェネリックなTask<T>型を使います。同期メソッドの戻り値がない(void)場合は、Task型(非ジェネリック)を返します。英語の動詞変形みたいな話です。この法則だけ覚えていれば、一旦同期的にコードを書いてみて、それから非同期処理に置き換えるという「練習」が出来ます。やっていれば、最初から非同期で全部書けるようになります。これらを使う事はまずありません(断言)。それでも、という方、では第2章へどうぞ。XAMLは省略します。TextBoxコントロールに”textBox”という名前を付けて定義してある前提です(また、本当はデータバインディングでやるべきです)。このコードだと、ダウンロードが完了するまで、UIが固まってしまいますね。メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。メソッド内でawaitキーワードを使ったら、戻り値の型はTask型で返さなければならない。Loadedイベントのシグネチャは変更できない。従って「void」のままとする。非同期処理対応メソッドは、「慣例」で「~Async」と命名する。ここでは「~Async」と命名しない。ReadFromUrlから非同期対応のReadFromUrlAsyncに変更する。ReadFromUrlAsyncがTask<string>を返すので、awaitする。awaitすると、stringが得られる。}}MainWindow_Loadedメソッドと、第1章のDownloadAsyncメソッドとの違いを確かめてください。RoutedEventArgs)」です。戻り値を返さない「void」であることが決められています。非同期メソッドはTask型かTask<T>型を返すべきですが、ここでは型が合いません。値を返さない場合に限り、このような非同期メソッドを「void」で定義します。ポイント:戻り値を返さず「void」と定義するメソッドは、イベントをフックしたハンドラメソッドのような場合しかありません。戻り値をvoidとした場合、呼び出し元は非同期処理の結果を追跡できず、処理結果を感知しません。逆に考えると、Loadedイベントの場合、そもそもイベントの呼び出し元は非同期処理の結果など気にしていません(voidなので)。voidとしても良い、と解釈することもできます。「Task」を返そうが「void」を返そうが、「await」キーワードを使ったので「async」キーワードを追加する必要があります。(何らかの)処理の開始位置が、このハンドラメソッドにある事にも注目してください。このように、呼び出し元(WPFのLoadedイベント処理)が、呼び出し先(MainWindow_Loadedメソッド)の事について感知していないような場合にのみ、呼び出し先の非同期メソッドの実装方法を(voidとすべきかどうか)考える必要があります。DownloadAsync();}第1章のDownloadAsyncメソッドを呼び出してみました。この呼び出しをawaitで待機する場合、Mainメソッドの先頭にasyncと付ける必要があります。このコードは問題なくコンパイルできますが、実行すると(殆どの場合)ダウンロードが完了することなくプログラムが終了します。Mainメソッドは、DownloadAsyncメソッドの非同期処理をawaitで待機します。これは「非同期的に待機」しているので、「Mainメソッドの処理(メインスレッド)は継続的に実行されている」のです。すると、メインスレッドはそのままMainメソッドを抜けてしまい、一瞬でプログラムが終了してしまいます。Task.Wait()を使わざるを得ない。Task.Resultで待機する必要があります。GUIアプリケーション(WPF/UWP/Xamarinなど)を実装している皆さんには、まったく縁のない話なので、このことは忘れても構いません。Task.Resultを使うとデッドロックを起こします。「デッドロックします。いつかTaskに「後ろから刺される」覚悟はしておいた方が良いでしょう...awaitで待機するように記述する事を「大前提」として、そうするにはどうやって書いたらいいかを考えると良いと思います。これだけで、「マトモ」な非同期処理コードを書くことが出来るはずです。繰り返しますが:C#の非同期処理(Taskとasync-await)を使ってコードを書くなら、これらの事を知っていれば9割がたの問題は回避できる!!ここで示した方法から外れたことをしようとしている場合は、殆ど間違いなく誤った記述をしようとしているので、もう一度何をしようとしているのか整理しよう!!これまでの経験で、WPF・UWPなどのGUIアプリケーションで、ここで紹介した方法「以外の」方法がどうしても必要となるパターンは、「一度もありません」でした。Task.Wait()やTask.Resultなどを必要としたケースは、MVVMフレームワークの設計時に、安全な非同期処理を実現するための、ごく内部的なコアロジックを設計したときだけです。これを読んで新たに疑問が湧いてくるかもしれません(例えば、どうしてデッドロックするのか、voidの非同期メソッドの結果はどうなるのか、など)。そのような場合は、より深く解説した記事を読んでみてください。WordPress%d人のブロガーが「いいね」をつけました。