ASP.NET Web APIを使用していくつかのHTTP要求をプロキシするアプリケーションを作成しており、断続的なエラーの原因を特定するのに苦労しています。競合状態のように見えますが…完全にはわかりません。
詳細を説明する前に、アプリケーションの一般的な通信フローを示します。
プロキシアプリケーションは、.NET 4.5を使用してASP.NET Web API RTMで記述されています。リレーを実行するコードは次のようになります。
//Controller entry point.
public HttpResponseMessage Post()
{
using (var client = new HttpClient())
{
var request = BuildRelayHttpRequest(this.Request);
//HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
//As it begins to filter in.
var relayResult = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
var returnMessage = BuildResponse(relayResult);
return returnMessage;
}
}
private static HttpRequestMessage BuildRelayHttpRequest(HttpRequestMessage incomingRequest)
{
var requestUri = BuildRequestUri();
var relayRequest = new HttpRequestMessage(incomingRequest.Method, requestUri);
if (incomingRequest.Method != HttpMethod.Get && incomingRequest.Content != null)
{
relayRequest.Content = incomingRequest.Content;
}
//Copies all safe HTTP headers (mainly content) to the relay request
CopyHeaders(relayRequest, incomingRequest);
return relayRequest;
}
private static HttpRequestMessage BuildResponse(HttpResponseMessage responseMessage)
{
var returnMessage = Request.CreateResponse(responseMessage.StatusCode);
returnMessage.ReasonPhrase = responseMessage.ReasonPhrase;
returnMessage.Content = CopyContentStream(responseMessage);
//Copies all safe HTTP headers (mainly content) to the response
CopyHeaders(returnMessage, responseMessage);
}
private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
var content = new PushStreamContent(async (stream, context, transport) =>
await sourceContent.Content.ReadAsStreamAsync()
.ContinueWith(t1 => t1.Result.CopyToAsync(stream)
.ContinueWith(t2 => stream.Dispose())));
return content;
}
断続的に発生するエラーは次のとおりです。
非同期操作がまだ保留中に、非同期モジュールまたはハンドラーが完了しました。
このエラーは通常、プロキシアプリケーションへの最初の数回のリクエストで発生し、その後エラーは再び表示されません。
Visual Studioは、スローされたときに例外をキャッチしません。ただし、エラーはGlobal.asax Application_Errorイベントでキャッチできます。残念ながら、例外にはスタックトレースがありません。
プロキシアプリケーションはAzure Webロールでホストされます。
犯人を特定するための助けをいただければ幸いです。
あなたの問題は微妙なものです:async
に渡すPushStreamContent
ラムダはasync void
として解釈されています( PushStreamContent
コンストラクターのため はパラメータとしてAction
sのみを取ります)。したがって、モジュール/ハンドラーの完了とそのasync void
ラムダの完了との間に競合状態があります。
PostStreamContent
は、ストリームのクローズを検出し、それをTask
(モジュール/ハンドラーの完了)の終わりとして扱います。したがって、まだできるasync void
メソッドがないことを確認する必要がありますストリームが閉じられた後に実行します。 async Task
メソッドは問題ないので、修正する必要があります。
private static PushStreamContent CopyContentStream(HttpResponseMessage sourceContent)
{
Func<Stream, Task> copyStreamAsync = async stream =>
{
using (stream)
using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
{
await sourceStream.CopyToAsync(stream);
}
};
var content = new PushStreamContent(stream => { var _ = copyStreamAsync(stream); });
return content;
}
プロキシを少し良くしたい場合は、Result
呼び出しをすべて削除することもお勧めします。
//Controller entry point.
public async Task<HttpResponseMessage> PostAsync()
{
using (var client = new HttpClient())
{
var request = BuildRelayHttpRequest(this.Request);
//HttpCompletionOption.ResponseHeadersRead - so that I can start streaming the response as soon
//As it begins to filter in.
var relayResult = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
var returnMessage = BuildResponse(relayResult);
return returnMessage;
}
}
以前のコードは、リクエストごとに1つのスレッドをブロックしていました(ヘッダーが受信されるまで)。 async
をコントローラーレベルまで使用することで、その間スレッドをブロックすることはありません。
少し単純なモデルでは、実際にHttpContentsを直接使用して、リレー内でそれらを渡すことができます。比較的簡単な方法でコンテンツをバッファリングせずに、リクエストとレスポンスの両方を非同期で信頼する方法を示すサンプルをアップロードしました。
必要に応じて接続を再利用できるため、同じHttpClientインスタンスを再利用することも有益です。
同じエラーでここに上陸した他の人にいくつかの知恵を加えたいと思いますが、あなたのコードはすべて問題ないようです。これが発生する場所からコールツリー全体で関数に渡されるラムダ式を探します。
MVC 5.xコントローラーアクションへのJavaScript JSON呼び出しでこのエラーが発生していました。スタックの上下で行うことはすべてasync Task
で定義され、await
を使用して呼び出されました。
ただし、Visual Studioの「次のステートメントを設定」機能を使用して、行を体系的にスキップして、どの行が原因かを判断しました。外部のNuGetパッケージの呼び出しに到達するまで、ローカルメソッドへのドリルダウンを続けました。呼び出されたメソッドはパラメータとしてAction
を取り、このActionに渡されたラムダ式の前にasync
キーワードがありました。 Stephen Clearyが上記の回答で指摘したように、これはasync void
として扱われますが、MVCはそれを好みません。幸いなことに、パッケージには同じメソッドの*非同期バージョンがあります。同じパッケージへのいくつかのダウンストリーム呼び出しとともに、それらを使用するように切り替えると、問題が修正されました。
私はこれが問題の新しい解決策ではないことを認識していますが、async void
またはasync <Action>
呼び出しがないと思ったため、このスレッドを何度か検索して問題を解決しようとしました。 、そして私は他の誰かがそれを避けるのを助けたかった。