私たちはHttpClientを使用してjsonをRESTful Webサービスに投稿しています。ある場合には、困惑する何かに遭遇しています。 postman、fiddlerなどのツールを使用して、エンドポイントに投稿し、それが機能していることを確認できます。 HttpClient.PostAsJsonAsyncで同じことを行うと、投稿先のソフトウェアで、データが正常に受信されたことを確認できます。ただし、PostAsJsonAsyncは応答を返すのではなく、常に最終的にタイムアウトになります。
私たちは使用しているサービスを作成したチームと協力して、私たちの側で追加のテストを行ってきましたが、そのサービスを本当にタイムアウトすることはまだできていません。
HttpClientで投稿を行うたびに、投稿先のソフトウェアが実際にデータを取得していることを確認できます。他のツールからそのターゲットソフトウェアへの投稿を行うたびに、ステータスコード200の応答が非常にすばやく表示されます。HttpClientに関する何かが、この特定のサービスからの応答の受け入れに失敗しています。私たちがここから何を見ることができるかについて誰かが知っていますか?
ここにコードがあります(それは私がそれが必要だとほとんど感じないほどクッキーカッターですが)
public string PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = client.PostAsJsonAsync(useUrl, o).Result;
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return response.Content.ReadAsStringAsync().Result;
}
return "";
}
}
System.Net.ServicePointManager.Expect100Continue = false;
私たちの場合、その1行のコードで問題が修正されました。別のチームの開発者がその提案を提供し、それが機能します。私はまだそれをググる必要があり、それが何に対処しているかの説明を提供するのに十分にそれを読んでいません。
この:
var response = client.PostAsJsonAsync(useUrl, o).Result;
コードでデッドロックが発生しています。これは、非同期APIでブロックする場合によく見られます。そのため、"応答が返されない"の影響が発生します。
これはどのようにしてデッドロックを引き起こしていますか?同期コンテキスト、おそらくUIに属するものを含む環境でこのリクエストを実行しているという事実。これは非同期要求を実行しており、応答が到着すると、IO完了スレッドを介して続行されます。完了スレッドは、同じ__UIコンテキストに継続をポストしようとしますが、現在.Result
呼び出しによってブロックされています。
HTTPリクエストを同期的に行いたい場合は、代わりにWebClient
を使用してください。非同期APIを適切に利用したい場合は、.Result
でブロックする代わりにawait
を使用します。
私は同じ問題を抱えていました this SO answer 修正しました。
簡単に言えば、デッドロックを回避するには、ConfigureAwait(false)
拡張機能を使用する必要があります。
var response = await client.PostAsJsonAsync(useUrl, o).ConfigureAwait(false);
非同期待機パターンに従っていない理由はありますか?非同期メソッドを呼び出していますが、それを待っていません。 RESTサービスを呼び出すコードがWindowsフォームまたはASP.NETアプリケーションであるかどうかは言いませんでしたが、.Result
は おそらくあなたに問題を引き起こしています です。
このようにメソッドを再構築できますか?
public async Task<string> PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
これは、@ Justin Helgersonのソリューションを少し変更したものです。 2つのブロッキング.Result
メソッドでの呼び出し。非同期になったら、両方を修正する必要があります。
public async Task<string> PostDataAsync(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
using (var client = new HttpClient())
{
if (timeoutMinutes > 0)
{
client.Timeout = new TimeSpan(0,timeoutMinutes,0);
}
var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
var response = await client.PostAsJsonAsync(useUrl, o);
if(response.StatusCode == System.Net.HttpStatusCode.OK)
{
return await response.Content.ReadAsStringAsync();
}
return "";
}
}
注 TAPパターン に従って、メソッドの名前もPostDataAsync
に変更しました。