HttpClientSendAsyncはメインスレッドをブロックします

Aug 21 2020

ローカルネットワーク内のすべてのIPアドレスにhttpリクエストを送信して、私の特定のデバイスを検出する小さなwinformsアプリケーションを作成しました。私の特定のサブネットマスクでは、512アドレスです。私はbackGroundWorkerを使用してこれを作成しましたが、同じことを実現するためにhttpClientとAsync / Awaitパターンを試してみたかったのです。以下のコードはhttpClientの単一インスタンスを使用しており、すべてのリクエストが完了するまで待ちます。この問題は、メインスレッドがブロックされることです。ピクチャーボックス+読み込みGIFがあり、均一にアニメーション化されていないため、これを知っています。ここで提案されているように、GetAsyncメソッドをTask.Runに配置しましたが、それも機能しませんでした。

    private async void button1_Click(object sender, EventArgs e)
    {
        var addresses = networkUtils.generateIPRange..
        await MakeMultipleHttpRequests(addresses);
    }


    public async Task MakeMultipleHttpRequests(IPAddress[] addresses)
    {
        List<Task<HttpResponseMessage>> httpTasks = new List<Task<HttpResponseMessage>>();
        foreach (var address in addresses)
        {
            Task<HttpResponseMessage> response = MakeHttpGetRequest(address.ToString());
            httpTasks.Add(response);
        }
        try
        {
            if (httpTasks.ToArray().Length != 0)
            {
                await Task.WhenAll(httpTasks.ToArray());
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("\thttp tasks did not complete Exception : {0}", ex.Message);
        }

    }

    private async Task<HttpResponseMessage> MakeHttpGetRequest(string address)
    {
        var url = string.Format("http://{0}/getStatus", address);
        var cts = new System.Threading.CancellationTokenSource();
        cts.CancelAfter(TimeSpan.FromSeconds(10));
        HttpResponseMessage response = null;
        var request = new HttpRequestMessage(HttpMethod.Get, url);
        response = await httpClient.SendAsync(request, cts.Token);
        return response;
    }

私はここで同様の問題を読みましたが、私のGUIスレッドはあまり機能していません。スレッドが不足している可能性があることをここで読みました。これは問題ですか、どうすれば解決できますか?コードを以下の単純なタスクに置き換えるとブロッキングが発生しないため、SendAsyncを知っています。

    await Task.Run(() =>
    {
       Thread.Sleep(1000);
    });

回答

2 Andy Aug 21 2020 at 05:31

したがって、ここでの問題の1つは、タスク作成の外部でタイムアウトを設定して、500以上のタスクをすばやく連続して作成していることです。

500以上のタスクを実行するように要求したからといって、500以上のタスクがすべて同時に実行されるわけではありません。それらは、スケジューラーがそれが可能であると判断したときにキューに入れられて実行されます。

10秒の作成時にタイムアウトを設定します。しかし、彼らは実行される前に10秒間スケジューラーにとどまることができました。

Httpリクエストを有機的にタイムアウトさせたい場合は、HttpClient:を作成するときにこのようにすることができます。

private static readonly HttpClient _httpClient = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(10)
};

したがって、タイムアウトをに移動するHttpClientと、メソッドは次のようになります。

private static Task<HttpResponseMessage> MakeHttpGetRequest(string address)
{
    return _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, new UriBuilder
    {
        Host = address,
        Path = "getStatus"
    }.Uri));
}

その方法を使用してみて、デバッグモードでのロックアップの問題が改善されるかどうかを確認してください。

あなた抱えていた問題に関する限り:あなたがデバッグモードにあり、デバッガーが「ねえ、あなたは例外がありました」と言っているので、それらはすべて同時にスポーンされたので 500回すべて同時にロックします。リリースモードで実行し、それでもロックされるかどうかを確認します。

私が検討したいのは、操作をバッチ処理することです。20を実行してから、それらの20が終了するまで待ち、さらに20を実行します。

タスクをバッチ処理する巧妙な方法をご覧になりたい場合は、お知らせください。喜んでお見せします。

1 PauloMorgado Aug 21 2020 at 17:03

.NET Frameworkでは、サーバーへの接続数はServicePointManagerクラスによって制御されます。

クライアントの場合、クライアントプロセスのデフォルトの接続制限は2です。

HttpClient.SendAsyncの呼び出しをいくつ行っても、同時にアクティブになるのは2つだけです。

ただし、接続は自分で管理できます。

.NET Coreでは、ここではサービスポイントマネージャーの概念ではなく、同等のデフォルト制限はint.MaxValueです。