質問: Util.Define.FAIL) { failLogin(); } else { successLogin(); } } ```
回答: こんにちは。 今どきのC#で非同期を書くなら、そもそも明示的にコールバックを使うことがないです。 何故なら、async/awaitとTaskが高度に抽象化されたコールバックの連鎖そのものだからです。 現状のLoginAsyncは非同期処理を投げっぱなし(`async void`)なので、これを`async Task`に書き換え、呼び出し元でawaitして分岐させます。 ``` // LoginResultみたいなenumとかが定義されてると仮定する public static async Task LoginAsync(string name, string password) { // // なんかの非同期処理をした後、 // if (response.ToString() == Util.Define.ERROR) { return LoginResult.Error; // 普通に結果をreturnする } else if (response.ToString() == Util.Define.FAIL) { return LoginResult.Fail; } else if ... } // 呼び出し元 { var result = await LoginAsync("name", "****"); // awaitして変数に代入 if (result == ...) // resultを見て次の処理を書く { ... } } ``` このように、asyncで定義したメソッドが結果を返すようにして、通常の同期メソッドと同じように、一連の流れを意識しつつ書くだけでいいのです。 循環参照を気にしておられるようですが、具体的にどのインスタンスがどのインスタンスと循環している(あるいは、しそう)だと思ったのでしょうか? 循環参照というのは、特定のインスタンス同士がお互いの参照をフィールドに保持している状態のことで、メソッド内での参照(スタック領域に確保される参照)は循環にはなりえません。 また、C#のGCは参照カウント方式ではないので、仮にインスタンス同士が循環参照していたとしても、それら全てのインスタンスへのルートからの参照が消滅した時点でGCの対象となるため、例えばstatic領域にListを置いて何かのインスタンスを詰め続けるみたいな危ういコーディングをしない限りは問題が起こることはありません。 その他、非同期に関する細かいことをちょっと書いておきました。 ### async voidについて async voidは基本的には**使用禁止**です。 戻り値を返す場合は`Task`を、同期メソッドにおける戻り値voidを表すには`Task`を返すようにしてください。 何故なら、async voidを書くとその実行結果をハンドルすることができない(まさに質問のコードのように、コールバックを使ってハンドルする方法しかない)ためです。 Taskを返すようにすることで、その自前の処理をasync/awaitによる非同期フローに組み込むことができるため、これを使わない理由はないです。 async voidを使ってよいのは、UIのイベントハンドラ(Button等)に非同期メソッドを使用する場面だけです。 ### using (HttpClient) HttpClientは実はusingで使うと困った問題を引き起こす設計上の欠陥が存在します(初心者殺し......)。 具体的には、何回もHttpClientの生成と破棄を繰り返すとソケットを食い潰します。 `RequestPostAsync`メソッドはまさに再利用を目的としたメソッド切り出しであるため、この場合HttpClientはstaticフィールドに確保し、usingを外して再利用したほうが良いです(ちゃんとしたインスタンス管理用のホルダを作るとなお良い)。 ### TaskのWait()のこと async/awaitを用いた非同期メソッドは、処理自体は非同期で実行されるが、「同階層の処理は全て同じスレッド上で実行しようとする」という設計になっています。 この仕様に起因するもので、非同期メソッドの設計自体に問題がなくても、その戻り値TaskをWait()しようとすると途端に致命的な問題が発生します。 デッドロックです。 WaitメソッドとResultプロパティの呼び出しは非常に危険なので、徹底的に使用を控えるようにしてください。 また、余裕があれば、`ConfigureAwait(false)`について情報収集を行っておくことをオススメします。 長くなっちゃったのでまとめます。 * コールバックなんて使わなくて、Taskでasync/awaitすればいい * 循環参照はstatic変数とかに入れてなければ大体問題ない * async voidは使わない * HttpClientはusing非推奨という罠がある * Wait()とResultはヤバイ