web-dev-qa-db-ja.com

HttpClient-リクエストのバッチを送信します

リクエストのバッチを繰り返し、HttpClientクラスを使用して各リクエストを外部APIに送信したいと思います。

  foreach (var MyRequest in RequestsBatch)
  {
            try
            {
                HttpClient httpClient = new HttpClient();
                httpClient.Timeout = TimeSpan.FromMilliseconds(5);
                HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), myRequest);
                JObject resultResponse = await response.Content.ReadAsAsync<JObject>();
            }
            catch (Exception ex)
            {
                continue;
            }
 }

ここでのコンテキストは、非常に小さいタイムアウト値を設定する必要があるため、応答にそれ以上の時間がかかる場合は、「タスクがキャンセルされました」という例外が発生し、繰り返しを続行します。

ここで、上記のコードで、次の2行にコメントを付けます。

                HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endpoint), myRequest);
                resultResponse = await response.Content.ReadAsAsync<JObject>();

反復は非常に速く終了します。コメントを外して、もう一度やり直してください。時間がかかります。

Awaitを使用してPostAsJsonAsync/ReadAsAsyncメソッドを呼び出すと、タイムアウト値よりも時間がかかるのではないでしょうか。

以下の回答に基づいて、異なるスレッドを作成すると仮定すると、次のメソッドがあります。

  public Task<JObject> GetResponse(string endPoint, JObject request, TimeSpan timeout)
    {
        return Task.Run(async () =>
        {
            try
            {
                HttpClient httpClient = new HttpClient();
                httpClient.Timeout = TimeSpan.FromMilliseconds(5);
                HttpResponseMessage response = await httpClient.PostAsJsonAsync<string>(string.Format("{0}api/GetResponse", endPoint), request).WithTimeout<HttpResponseMessage>(timeout);
                JObject resultResponse = await response.Content.ReadAsAsync<JObject>().WithTimeout<JObject>(timeout);
                return resultResponse;
            }
            catch (Exception ex)
            {
                return new JObject() { new JProperty("ControlledException", "Invalid response. ")};
            }
        });
    }

そこで例外が発生し、JObject例外が非常に高速に返されるはずですが、httpClientメソッドを使用している場合は、例外が発生したとしても、かなりの時間がかかります。戻り値が単純な例外JObjectであったとしても、タスクに影響を与える舞台裏の処理はありますか?

はいの場合、非常に高速な方法でリクエストのバッチをAPIに送信するために使用できる別のアプローチはどれですか?

8

実際には、リクエストごとに個別のスレッドを実行しているようには見えません。次のようなものを試してください。

var taskList = new List<Task<JObject>>();

foreach (var myRequest in RequestsBatch)
{
    taskList.Add(GetResponse(endPoint, myRequest));
}

try
{
    Task.WaitAll(taskList.ToArray());
}
catch (Exception ex)
{
}

public Task<JObject> GetResponse(string endPoint, string myRequest)
{
    return Task.Run(() =>
        {
            HttpClient httpClient = new HttpClient();

            HttpResponseMessage response = httpClient.PostAsJsonAsync<string>(
                 string.Format("{0}api/GetResponse", endpoint), 
                 myRequest, 
                 new CancellationTokenSource(TimeSpan.FromMilliseconds(5)).Token);

            JObject resultResponse = response.Content.ReadAsAsync<JObject>();
        });
}
2
RagtimeWilly

私は、物事をスピードアップするための鍵はリクエストを並行して実行することであるという点で、受け入れられた答えに同意します。ただし、Task.RunまたはParallel.ForEachを使用して追加のスレッドを強制的にミックスに追加するソリューションでは、I/Oバウンドの非同期操作で効率が向上しません。どちらかといえば痛いです。

基盤となる非同期サブシステムに、タスクを可能な限り効率的に完了するために必要なスレッドの数を決定させながら、すべての呼び出しを簡単に同時に実行できます。応答を待っている間はスレッドをまったく必要としないため、同時呼び出しの数よりもはるかに少ない可能性があります。

さらに、受け入れられた回答は、呼び出しごとにHttpClientの新しいインスタンスを作成します。それもしないでください- 悪いことが起こる可能性があります

承認された回答の修正版は次のとおりです。

var httpClient = new HttpClient {
    Timeout = TimeSpan.FromMilliseconds(5)
};

var taskList = new List<Task<JObject>>();

foreach (var myRequest in RequestsBatch)
{
    // by virtue of not awaiting each call, you've already acheived parallelism
    taskList.Add(GetResponseAsync(endPoint, myRequest));
}

try
{
    // asynchronously wait until all tasks are complete
    await Task.WhenAll(taskList.ToArray());
}
catch (Exception ex)
{
}

async Task<JObject> GetResponseAsync(string endPoint, string myRequest)
{
    // no Task.Run here!
    var response = await httpClient.PostAsJsonAsync<string>(
        string.Format("{0}api/GetResponse", endpoint), 
        myRequest);
    return await response.Content.ReadAsAsync<JObject>();
}
28
Todd Menier