質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

解決済

1回答

3983閲覧

[C#]非同期通信処理とそのコールバック方法について

bbdd

総合スコア43

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

1クリップ

投稿2018/03/30 03:02

編集2018/03/30 09:18

前提

・WPFアプリ
・Visual Studio
・C#

お聞きしたいこと

API通信処理を記述しているのですが、
・非同期通信とその際のコールバック実装てこれでいいの?
と悩んでおり、アドバイス等頂ければ幸いです。

→ 実装済みで期待通りの動作はしています。
ただ、C#の経験が浅くまたデスクトップアプリを作成するのが初めてなので、自分以外の人が見て、このコードだと潜在的に○○な問題を孕んでいる、とかここはこういう書き方したほうが効率的だよ、といった意見があればお聞きしたいです。

コード

以下がPostの非同期通信処理メソッド、ログインAPIを叩くメソッドのコードです。
両方ともAPIManagerという通信処理関連のメソッドをまとめたクラスにて記述しており、ここのメソッドを他のクラスで使用する想定です。

↓Post通信を非同期で行うメソッドです。
・通信の汎用メソッドとして使用
・成功したらJTokenを返す
・通信エラーなら"error"という文字列を返す
・レスポンスが200以外なら"fail"とい文字列を返す

public static async Task<JToken> RequestPostAsync(string url, Dictionary<string, object> contentData) { var contentJson = Newtonsoft.Json.JsonConvert.SerializeObject(contentData); HttpContent content = new StringContent(contentJson); content.Headers.Add("hoge", "hoge"); content.Headers.Add("fuga", "fuga"); // 非同期処理実行 using (HttpClient client = new HttpClient(new WebRequestHandler { CachePolicy = new System.Net.Cache.HttpRequestCachePolicy(System.Net.Cache.HttpRequestCacheLevel.NoCacheNoStore) })) { client.Timeout = TimeSpan.FromMilliseconds(10000); HttpResponseMessage response; try { response = await client.PostAsync(url, content); } catch (HttpRequestException) { System.Diagnostics.Debug.WriteLine("通信エラー"); return Util.Define.ERROR; } if (response.IsSuccessStatusCode) { string result = await response.Content.ReadAsStringAsync(); if (result != "") { JToken jToken = JToken.Parse(result); return jToken; } else { // レスポンスで受け取る情報なし return ""; } } else { System.Diagnostics.Debug.WriteLine("通信失敗(fail) statusCode:" + response.StatusCode); return Util.Define.FAIL; } }

↓上記メソッドを用いて例えばログインAPIを以下のように実装しています
・引数にて通信終了後に実行するメソッドを受け取る
→ 例えば他のクラスでこのメソッドを使用し、通信成功時にTextBlockのTextを変更するメソッドを引数としてわたしていたら循環参照起きる??(weakの設定が必要かなと思っています)

public static async void LoginAsync(string name, string password, Action successLogin, Action failLogin, Action errorLogin) { var content = new Dictionary<string, Object>() { {"name:", name}, {"pass", password} }; var response = await RequestPostAsync("loginApiPass", content, ""); if (response.ToString() == Util.Define.ERROR) { errorLogin(); } else if (response.ToString() == Util.Define.FAIL) { failLogin(); } else { successLogin(); } }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2018/03/30 08:00

テストしてみたらいいのでは?
bbdd

2018/03/30 09:12

情報が不足しておりすみません。 実行して非同期通信、コールバックが動いているのは確認できています。質問の意図としては、「(私はC#の経験が浅く、またデスクトップアプリを作成するのが初めてなので、)自分以外の人が見て、このコードだと(動くけども)潜在的に○○な問題を孕んでいる、とかここはこういう書き方したほうが効率的だよ、といった意見があれば聞きたい」ということでした。質問文の不備ですので修正させていただきます。
guest

回答1

0

ベストアンサー

こんにちは。

今どきのC#で非同期を書くなら、そもそも明示的にコールバックを使うことがないです。
何故なら、async/awaitとTaskが高度に抽象化されたコールバックの連鎖そのものだからです。
現状のLoginAsyncは非同期処理を投げっぱなし(async void)なので、これをasync Task<LoginResult>に書き換え、呼び出し元でawaitして分岐させます。

csharp

1// LoginResultみたいなenumとかが定義されてると仮定する 2public static async Task<LoginResult> LoginAsync(string name, string password) 3{ 4 // 5 // なんかの非同期処理をした後、 6 // 7 8 if (response.ToString() == Util.Define.ERROR) 9 { 10 return LoginResult.Error; // 普通に結果をreturnする 11 } 12 else if (response.ToString() == Util.Define.FAIL) 13 { 14 return LoginResult.Fail; 15 } 16 else if ... 17} 18 19// 呼び出し元 20{ 21 var result = await LoginAsync("name", "****"); // awaitして変数に代入 22 23 if (result == ...) // resultを見て次の処理を書く 24 { 25 ... 26 } 27}

このように、asyncで定義したメソッドが結果を返すようにして、通常の同期メソッドと同じように、一連の流れを意識しつつ書くだけでいいのです。


循環参照を気にしておられるようですが、具体的にどのインスタンスがどのインスタンスと循環している(あるいは、しそう)だと思ったのでしょうか?
循環参照というのは、特定のインスタンス同士がお互いの参照をフィールドに保持している状態のことで、メソッド内での参照(スタック領域に確保される参照)は循環にはなりえません。
また、C#のGCは参照カウント方式ではないので、仮にインスタンス同士が循環参照していたとしても、それら全てのインスタンスへのルートからの参照が消滅した時点でGCの対象となるため、例えばstatic領域にListを置いて何かのインスタンスを詰め続けるみたいな危ういコーディングをしない限りは問題が起こることはありません。


その他、非同期に関する細かいことをちょっと書いておきました。

async voidについて

async voidは基本的には使用禁止です。
戻り値を返す場合はTask<T>を、同期メソッドにおける戻り値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はヤバイ

投稿2018/03/30 16:43

tamoto

総合スコア4103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問