HttpClient SendAsync blokuje główny wątek

Aug 21 2020

Napisałem małą aplikację winForms, która wysyła żądania http na każdy adres IP w mojej sieci lokalnej, aby wykryć pewne moje urządzenie. Na mojej masce podsieci to 512 adresów. Napisałem to za pomocą backGroundWorker, ale chciałem wypróbować httpClient i wzorzec Async / Await, aby osiągnąć to samo. Poniższy kod używa jednego wystąpienia httpClient i czekam, aż wszystkie żądania zostaną zakończone. Problem polega na tym, że główny wątek zostaje zablokowany. Wiem to, ponieważ mam okno ze zdjęciami + ładowanie gifa i nie jest ono jednolicie animowane. Umieściłem metodę GetAsync w Task.Run zgodnie z sugestią tutaj, ale to też nie zadziałało.

    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;
    }

Przeczytałem tutaj podobny problem , ale mój wątek gui niewiele robi. Czytałem tutaj , że być może kończą mi się wątki. Czy to jest problem, jak mogę go rozwiązać? Wiem, że to Send Async, ponieważ jeśli zastąpię kod prostym zadaniem poniżej, nie ma blokowania.

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

Odpowiedzi

2 Andy Aug 21 2020 at 05:31

Jednym z problemów jest to, że tworzysz ponad 500 zadań jedno po drugim w krótkich odstępach czasu z limitem czasu ustawionym poza tworzeniem zadania.

Tylko dlatego, że prosisz o uruchomienie ponad 500 zadań, nie oznacza, że ​​ponad 500 zadań będzie wykonywanych w tym samym czasie. Są ustawiane w kolejce i uruchamiane, gdy planista uzna to za możliwe.

Ustawiłeś limit czasu w momencie tworzenia 10 sekund. Ale mogli siedzieć w programie planującym przez 10 sekund, zanim jeszcze zostaną wykonane.

Jeśli chcesz, aby żądania HTTP miały limit czasu organicznego, możesz to zrobić w ten sposób, gdy tworzysz HttpClient:

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

Tak więc, przesuwając limit czasu do HttpClient, twoja metoda powinna teraz wyglądać następująco:

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

Spróbuj użyć tej metody i sprawdź, czy poprawi to problem z blokowaniem w trybie debugowania.

Jeśli chodzi o kwestię ty zostały posiadające: To blokowania się dlatego, że jesteś w trybie debugowania i debugger próbuje powiedzieć „hej, masz wyjątek” 500 razy wszystkie w tym samym czasie , ponieważ wszystkie były zrodził się w tym samym czasie . Uruchom go w trybie wydania i sprawdź, czy nadal się blokuje.

To, co rozważałbym zrobienie, to podzielenie twoich operacji. Zrób 20, a następnie poczekaj, aż te 20 zakończą się, zrób 20 więcej, i tak dalej.

Jeśli chcesz zobaczyć zgrabny sposób grupowania zadań, daj mi znać, a z przyjemnością Ci pokażę.

1 PauloMorgado Aug 21 2020 at 17:03

W .NET Framework liczba połączeń z serwerem jest kontrolowana przez klasę ServicePointManager .

W przypadku klienta domyślny limit połączeń w procesach klienta wynosi 2 .

Bez względu na to, ile wywołań HttpClient.SendAsync wykonasz, tylko 2 będą aktywne w tym samym czasie.

Ale możesz samodzielnie zarządzać połączeniami .

W przypadku platformy .NET Core nie jest to koncepcja menedżera punktów usług, a równoważny domyślny limit to int.MaxValue.