HttpClient SendAsync가 메인 스레드를 차단합니다.

Aug 21 2020

내 특정 장치를 찾기 위해 로컬 네트워크 내의 모든 IP 주소에 http 요청을 보내는 작은 winforms 응용 프로그램을 작성했습니다. 내 특정 서브넷 마스크에서 512 개 주소입니다. 나는 backGroundWorker를 사용하여 이것을 작성했지만 httpClient와 Async / Await 패턴을 사용해 똑같은 결과를 얻고 싶었습니다. 아래 코드는 httpClient의 단일 인스턴스를 사용하며 모든 요청이 완료 될 때까지 기다립니다. 이 문제는 주 스레드가 차단된다는 것입니다. 나는 picturebox + 로딩 gif가 있고 균일하게 애니메이션되지 않기 때문에 이것을 알고 있습니다. 여기에 제안 된대로 Task.Run에 GetAsync 메서드를 넣었 지만 작동하지 않았습니다.

    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 스레드가 많이하지 않습니다. 나는 여기 에서 스레드가 부족할 수 있다고 읽었습니다 . 이것이 문제입니까? 어떻게 해결할 수 있습니까? 아래의 간단한 작업으로 코드를 바꾸면 차단이 없기 때문에 Send Async를 알고 있습니다.

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

답변

2 Andy Aug 21 2020 at 05:31

따라서 여기서 문제 중 하나는 작업 생성 외부에 시간 제한을 설정하여 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입니다.