編集:この質問 は同じ問題かもしれませんが、応答がありません...
編集:テストケース5では、タスクはWaitingForActivation
状態で止まっています。
.NET 4.5でSystem.Net.Http.HttpClientを使用したときに奇妙な動作が発生しました。(例)httpClient.GetAsync(...)
への呼び出しの結果を「待つ」ことは決して返されません。
これは、新しいasync/await言語機能とTasks APIを使用しているときに特定の状況でのみ発生します。コードは継続のみを使用しているときに常に機能するようです。
これは問題を再現するコードです。これをVisual Studio 11の新しい "MVC 4 WebApiプロジェクト"にドロップして、次のGETエンドポイントを公開します。
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
ここでの各エンドポイントは、完了しない/api/test5
を除いて同じデータ(stackoverflow.comからの応答ヘッダー)を返します。
再現するコード:
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つのスレッドのみが要求を処理できます。必要に応じていくつかの並列処理を実行できます(スレッドプールから追加のスレッドを借りる)が、1つのスレッドのみが要求コンテキストを持ちます(追加のスレッドには要求コンテキストがありません)。
これは ASP.NET SynchronizationContext
で管理 です。
デフォルトでは、await
a Task
の場合、キャプチャされたSynchronizationContext
(またはTaskScheduler
がない場合はキャプチャされたSynchronizationContext
)でメソッドが再開します。通常、これはまさにあなたが望むものです:非同期コントローラーアクションはawait
何かをし、再開すると、リクエストコンテキストで再開します。
したがって、test5
が失敗する理由は次のとおりです。
Test5Controller.Get
はAsyncAwait_GetSomeDataAsync
を実行します(ASP.NET要求コンテキスト内)。AsyncAwait_GetSomeDataAsync
はHttpClient.GetAsync
を実行します(ASP.NET要求コンテキスト内)。HttpClient.GetAsync
は未完了のTask
を返します。AsyncAwait_GetSomeDataAsync
はTask
を待ちます;完全ではないため、AsyncAwait_GetSomeDataAsync
は未完了のTask
を返します。Test5Controller.Get
blocksTask
が完了するまで現在のスレッドをブロックします。HttpClient.GetAsync
によって返されたTask
が完了します。AsyncAwait_GetSomeDataAsync
は、ASP.NET要求コンテキスト内で再開を試みます。ただし、そのコンテキストにはすでにスレッドがあります:Test5Controller.Get
でブロックされているスレッド。他のものが機能する理由は次のとおりです。
test1
、test2
、およびtest3
):Continuations_GetSomeDataAsync
は、スレッドプールへの継続をスケジュールします。outsideASP.NET要求コンテキスト。これにより、Continuations_GetSomeDataAsync
によって返されるTask
を、要求コンテキストを再入力せずに完了することができます。test4
およびtest6
):Task
はawaitedであるため、ASP.NET要求スレッドはブロックされません。これにより、AsyncAwait_GetSomeDataAsync
は、続行する準備ができたときにASP.NET要求コンテキストを使用できます。そして、ここにベストプラクティスがあります。
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
を使用する方法の簡単な説明が含まれています。SynchronizationContext
は、要求コンテキストを一度に1つのスレッドのみに制限します。2012-07-13の更新:この回答を組み込みました ブログ投稿へ 。
編集:デッドロックを回避するための最後の努力として以外は、一般的に以下のことを避けるようにしてください。 Stephen Clearyからの最初のコメントを読んでください。
からのクイックフィックスはこちら 。書く代わりに:
Task tsk = AsyncOperation();
tsk.Wait();
試してください:
Task.Run(() => AsyncOperation()).Wait();
あるいは結果が必要な場合は
var result = Task.Run(() => AsyncOperation()).Result;
ソースから(上の例に合うように編集された):
AsyncOperationは、SynchronizationContextが存在しないThreadPool上で呼び出されるようになり、AsyncOperation内で使用されている継続は、呼び出し元のスレッドに強制的に戻されることはありません。
私には、これを完全に非同期にするオプションはありません(これが望ましい)ので、これは使用可能なオプションのように見えます。
ソースから:
FooAsyncメソッドでの待機で、元に戻すコンテキストが見つからないことを確認してください。そのための最も簡単な方法は、Task.Runで呼び出しをラップするなど、ThreadPoolから非同期の作業を呼び出すことです。
int Sync(){return Task.Run(()=> Library.FooAsync())。結果; }
FooAsyncは、SynchronizationContextが存在しないThreadPool上で呼び出されるようになり、FooAsync内で使用されている継続は、Sync()を呼び出しているスレッドに強制的に戻されることはありません。
.Result
、.Wait
またはawait
を使用しているため、コード内でデッドロックが発生します。
デッドロックを防ぐためにasync
メソッドでConfigureAwait(false)
を使うことができます
このような:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
「非同期コードをブロックしない」では、可能な限り
ConfigureAwait(false)
を使用できます。
これら二つの学校は、本当に除外されていません。
これはあなたが単に使用しなければならないシナリオです。
Task.Run(() => AsyncOperation()).Wait();
または何かのような
AsyncContext.Run(AsyncOperation);
データベーストランザクション属性のMVCアクションがあります。何かがうまくいかない場合、考えは(おそらく)アクションで行われたすべてをロールバックすることでした。これはコンテキストの切り替えを許可しません。そうでなければ、トランザクションのロールバックまたはコミットは失敗します。
私が必要とするライブラリはasyncを実行することが期待されているのでasyncです。
唯一の選択肢です。通常の同期呼び出しとして実行してください。
私はただお互いに言っているだけです。
私はここを見ています。
http://msdn.Microsoft.com/ja-jp/library/system.runtime.compilerservices.taskawaiter(v = vs.110).aspx
そしてここ:
そして見て:
この型とそのメンバは、コンパイラで使用するためのものです。
await
バージョンが機能すること、そして物事を行うための「正しい」方法であることを考えると、この質問に対する回答が本当に必要ですか?
私の投票は:APIの誤用です。
私はここでOPへの直接の関連性より完全性のためにこれをもっと入れるつもりです。私は1日近くかけてHttpClient
リクエストをデバッグしましたが、なぜ応答が返ってこなかったのか疑問に思いました。
最後に、呼び出しスタックのさらに下のawait
呼び出しをasync
に忘れていたことがわかりました。
セミコロンがないのと同じくらい気持ちいいです。