[解決済み] await/asyncを使用しているときにHttpClient.GetAsync(...)が返らない
質問
編集してください。 この質問 同じ問題のように見えますが、回答がありません...。
編集する
テストケース5では、タスクが
WaitingForActivation
の状態です。
.NET 4.5 の System.Net.Http.HttpClient を使用して、奇妙な動作に遭遇しました - (例) への呼び出しの結果を "await" する場合。
httpClient.GetAsync(...)
は決して戻りません。
これは、新しい async/await 言語機能と Tasks API を使用した場合にのみ発生する現象で、継続のみを使用した場合は常にコードが動作するようです。
Visual Studio 11 の新しい "MVC 4 WebApi project" にこのコードをドロップして、次の GET エンドポイントを公開します。
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
ここでの各エンドポイントは、同じデータ (stackoverflow.com からのレスポンスヘッダ) を返します。
/api/test5
は決して完了しません。
HttpClientクラスのバグに遭遇したのか、それともAPIの使い方を間違えているのでしょうか?
再現するためのコード
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
解決方法は?
APIを誤って使用している。
ASP.NETでは、一度に1つのスレッドしかリクエストを処理できないのです。必要であれば並列処理を行うことができますが (スレッドプールから追加のスレッドを借りる)、 リクエストコンテキストを持つスレッドはひとつだけです (追加のスレッドはリクエストコンテキストを持ちません)。
これは
ASP.NETで管理される
SynchronizationContext
.
デフォルトでは
await
a
Task
をキャプチャすると、メソッドが再開されます。
SynchronizationContext
(またはキャプチャされた
TaskScheduler
がない場合は
SynchronizationContext
). 通常、これはちょうどあなたが望むことです。非同期コントローラアクションが
await
を実行し、再開するときにはリクエストのコンテキストで再開します。
では、なぜ
test5
は失敗します。
-
Test5Controller.Get
が実行されます。AsyncAwait_GetSomeDataAsync
(ASP.NETのリクエストコンテキスト内で)。 -
AsyncAwait_GetSomeDataAsync
が実行されます。HttpClient.GetAsync
(ASP.NETのリクエストコンテキスト内で)。 -
HTTP リクエストが送信され
HttpClient.GetAsync
は、未完成のTask
. -
AsyncAwait_GetSomeDataAsync
を待ちます。Task
;は完全ではないので。AsyncAwait_GetSomeDataAsync
は、未完成のTask
. -
Test5Controller.Get
ブロック を実行するまで、現在のスレッドをTask
が完了します。 -
HTTPレスポンスが来て
Task
が返すHttpClient.GetAsync
が完了する。 -
AsyncAwait_GetSomeDataAsync
はASP.NETリクエストコンテキスト内で再開を試みます。しかし、そのコンテキストにはすでにスレッドが存在します。Test5Controller.Get
. - デッドロック
他のものはなぜ動くのか、その理由を説明します。
-
(
test1
,test2
およびtest3
):Continuations_GetSomeDataAsync
は、スレッドプールに継続をスケジュールします。 外側 ASP.NETのリクエストコンテキストを使用します。これによりTask
が返すContinuations_GetSomeDataAsync
を使えば、 リクエストコンテキストを再入力することなく完了します。 -
(
test4
とtest6
): というのはTask
は 待機中 の場合、ASP.NETリクエストスレッドはブロックされません。このためAsyncAwait_GetSomeDataAsync
を使用して、継続の準備ができたときにASP.NETリクエストコンテキストを使用することができます。
そして、ベストプラクティスはこちらです。
-
あなたの"library"で。
async
メソッドを使用します。ConfigureAwait(false)
は可能な限り使用します。あなたの場合、次のように変更します。AsyncAwait_GetSomeDataAsync
をvar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
-
でブロックしないでください。
Task
sです。async
をずっと使っています。言い換えればawait
の代わりにGetResult
(Task.Result
とTask.Wait
に置き換える必要があります。await
).
そうすることで、継続(の残り)という両方のメリットを得ることができます。
AsyncAwait_GetSomeDataAsync
メソッド) は ASP.NET リクエストコンテキストに入る必要のない基本的なスレッドプールスレッドで実行されます。
async
(これはリクエストスレッドをブロックしません)。
詳細はこちら
-
私の
async
/await
イントロポスト という簡単な説明が含まれています。Task
を使用しています。SynchronizationContext
. -
は
非同期/待機に関するFAQ
で、そのコンテキストについて詳しく説明しています。また
Await、UI、デッドロック! なんてね。
どの
する
は、UIではなくASP.NETであるにもかかわらず、ここに適用されます。なぜなら、ASP.NETの
SynchronizationContext
は、リクエストコンテキストを一度にひとつのスレッドに制限しています。 - これは MSDNフォーラムへの投稿 .
- スティーブン・トゥブ このデッドロックのデモ (UIを使用) 、および Lucian Wischikも同様です。 .
2012-07-13に更新しました。 この回答を組み入れました ブログ記事へ .
関連
-
[解決済み】Sequence contains no matching element(シーケンスにマッチする要素がない
-
[解決済み】Nullableオブジェクトは値を持たなければならない?
-
[解決済み] forEachループでasync/awaitを使用する
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] async」と「await」の使い方とタイミング
-
[解決済み] async/await関数を並列に呼び出す
-
[解決済み] async/await - タスクとvoidをいつ返すか?
-
[解決済み] 非同期関数+await+setTimeoutの組合せ
-
[解決済み] 複数のタスクにasync/awaitを使用する
-
[解決済み] Task.Runの正しい使い方とasync-awaitだけの使い方
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み] 保護レベルによりアクセス不能になりました。
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み] 'SubSonic.Schema .DatabaseColumn' 型のオブジェクトをシリアライズする際に、循環参照が検出されました。
-
[解決済み】プロジェクトビルド時のエラー。エディタでスクリプトにコンパイルエラーがあるため、Playerのビルドにエラーが発生する
-
[解決済み】値が期待した範囲に収まらない
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】"指定されたパスのフォーマットはサポートされていません。"
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?