HttpWebRequest (.NET)를 비동기 적으로 사용하는 방법은 무엇입니까?


156

HttpWebRequest (.NET, C #)를 비동기 적으로 사용하려면 어떻게해야합니까?


1
이 개발자 퓨전에 기사 확인 developerfusion.com/code/4654/asynchronous-httpwebrequest

Jason이 요구하는 것을 수행하는 꽤 완전한 예를 보려면 다음을 참조하십시오. stuff.seans.com/2009/01/05/… Sean
Sean Sexton


1
잠시 동안, 당신이 재귀 스레드에 대해 언급하려고하는지 궁금합니다.
Kyle Hodgson

답변:


125

사용하다 HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

콜백 함수는 비동기 작업이 완료되면 호출됩니다. 최소한 EndGetResponse()이 함수에서 호출해야 합니다.


16
BeginGetResponse는 비동기 사용에 유용하지 않습니다. 리소스에 연결하려고 시도하는 동안 차단 된 것 같습니다. 네트워크 케이블을 분리하거나 잘못된 URI를 제공 한 다음이 코드를 실행하십시오. 대신 제공하는 두 번째 스레드에서 GetResponse를 실행해야합니다.
애쉬

2
@AshleyHenderson-샘플을 제공해 주시겠습니까?
Tohid

1
@Tohid Unity3D와 함께 사용한 샘플이 포함 된 전체 클래스입니다 .
cregox

3
webRequest.Proxy = null요청 속도를 크게 높이려면 추가해야합니다 .
Trontor

C #
은이

67

답을 고려할 때 :

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

요청 포인터 또는 다음과 같은 다른 객체를 보낼 수 있습니다.

void StartWebRequest()
{
    HttpWebRequest webRequest = ...;
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), webRequest);
}

void FinishWebRequest(IAsyncResult result)
{
    HttpWebResponse response = (result.AsyncState as HttpWebRequest).EndGetResponse(result) as HttpWebResponse;
}

인사말


7
'요청'변수의 범위를 벗어나지 않는 옵션의 경우 +1이지만 "as"키워드 대신 캐스트를 수행 할 수 있습니다. InvalidCastException이는 대신에 혼동의 NullReferenceException이의 던져 질 것
다비 Fiamenghi을

64

BeginGetResponse()현재 스레드에서 일부 작업을 수행 하기 때문에 지금까지 모두 잘못되었습니다 . 로부터 문서 :

BeginGetResponse 메소드는이 메소드가 비 동기화되기 전에 일부 동기 설정 태스크 (DNS 분석, 프록시 감지 및 TCP 소켓 연결)를 완료해야합니다. 결과적으로 오류에 대한 예외가 발생하기 전에 초기 동기 설정 작업을 완료하는 데 상당한 시간 (네트워크 설정에 따라 최대 몇 분)이 걸릴 수 있으므로 UI ​​(사용자 인터페이스) 스레드에서이 메소드를 호출하면 안됩니다. 방법은 성공합니다.

그래서 이것을 올바르게하려면 :

void DoWithResponse(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            var response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

그런 다음 응답으로 필요한 작업을 수행 할 수 있습니다. 예를 들면 다음과 같습니다.

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});

2
await를 사용하여 HttpWebRequest의 GetResponseAsync 메소드를 호출 할 수 있습니까 (함수를 비동기로 가정 한 경우)? C #을 처음 접했으므로이 작업이 완전히 어색해졌습니다.
Brad

.NET 4.5 (현재 베타)가 필요하지만 GetResponseAsync가 좋아 보입니다.
Isak

15
예수. 그것은 추악한 코드입니다. 비동기 코드를 읽을 수없는 이유는 무엇입니까?
John Shedletsky

왜 request.BeginGetResponse ()가 필요합니까? wrapperAction.BeginInvoke ()로 충분하지 않은 이유는 무엇입니까?
Igor Gatis

2
@Gatis 두 가지 수준의 비동기 호출이 있습니다. wrapperAction.BeginInvoke ()는 두 번째 비동기 호출 인 request.BeginGetResponse ()를 호출하는 람다 식에 대한 첫 번째 비동기 호출입니다. Isak이 지적했듯이 BeginGetResponse ()에는 일부 동기 설정이 필요하므로 추가 비동기 호출로 래핑합니다.
walkingTarget

64

가장 쉬운 방법은 TPL 에서 TaskFactory.FromAsync 를 사용하는 것 입니다. 새로운 async / await 키워드 와 함께 사용하면 말 그대로 몇 줄의 코드입니다 .

var request = WebRequest.Create("http://www.stackoverflow.com");
var response = (HttpWebResponse) await Task.Factory
    .FromAsync<WebResponse>(request.BeginGetResponse,
                            request.EndGetResponse,
                            null);
Debug.Assert(response.StatusCode == HttpStatusCode.OK);

C # 5 컴파일러를 사용할 수 없으면 Task.ContinueWith 메서드를 사용하여 위의 작업을 수행 할 수 있습니다 .

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });

.NET 4부터이 TAP 방식이 바람직합니다. MS의 "예제 : 작업에서 EAP 패턴 래핑"과 유사한 예제를 참조하십시오 ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Alex Klaus

다른 방법보다 훨씬 쉬운 방법
Don Rolling

8

BackgroundWorker를 사용하여 결국 위의 솔루션과 달리 비동기식이며 GUI 스레드로 돌아가는 것을 처리하며 이해하기가 매우 쉽습니다.

예외가 RunWorkerCompleted 메소드로 끝나기 때문에 예외를 처리하는 것도 매우 쉽지만 다음을 읽으십시오. BackgroundWorker의 처리되지 않은 예외

WebClient를 사용했지만 원하는 경우 HttpWebRequest.GetResponse를 사용할 수 있습니다.

var worker = new BackgroundWorker();

worker.DoWork += (sender, args) => {
    args.Result = new WebClient().DownloadString(settings.test_url);
};

worker.RunWorkerCompleted += (sender, e) => {
    if (e.Error != null) {
        connectivityLabel.Text = "Error: " + e.Error.Message;
    } else {
        connectivityLabel.Text = "Connectivity OK";
        Log.d("result:" + e.Result);
    }
};

connectivityLabel.Text = "Testing Connectivity";
worker.RunWorkerAsync();

7
public static async Task<byte[]> GetBytesAsync(string url) {
    var request = (HttpWebRequest)WebRequest.Create(url);
    using (var response = await request.GetResponseAsync())
    using (var content = new MemoryStream())
    using (var responseStream = response.GetResponseStream()) {
        await responseStream.CopyToAsync(content);
        return content.ToArray();
    }
}

public static async Task<string> GetStringAsync(string url) {
    var bytes = await GetBytesAsync(url);
    return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}

6

이 답변 중 다수가 게시 된 후 .NET이 변경되었으며보다 최신 답변을 제공하고 싶습니다. Task백그라운드 스레드에서 실행될 비동기 메소드를 시작하려면 다음을 수행하십시오 .

private async Task<String> MakeRequestAsync(String url)
{    
    String responseText = await Task.Run(() =>
    {
        try
        {
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            WebResponse response = request.GetResponse();            
            Stream responseStream = response.GetResponseStream();
            return new StreamReader(responseStream).ReadToEnd();            
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: " + e.Message);
        }
        return null;
    });

    return responseText;
}

비동기 메소드를 사용하려면 다음을 수행하십시오.

String response = await MakeRequestAsync("http://example.com/");

최신 정보:

이 솔루션은 WebRequest.GetResponseAsync()대신 사용 하는 UWP 앱에서는 작동 WebRequest.GetResponse()하지 않으며 Dispose()적절한 경우 메서드를 호출하지 않습니다 . @dragansr에는 이러한 문제를 해결하는 훌륭한 대안 솔루션이 있습니다.


1
감사합니다 ! 복잡한 예를 사용하는 비동기 예, 많은 예를 찾으려고 노력해 왔습니다.
WDUK

각 응답에 대한 스레드를 차단하지 않습니까? docs.microsoft.com/en-us/dotnet/standard/parallel-programming/
Pete Kirkham

@PeteKirkham 백그라운드 스레드가 UI 스레드가 아닌 요청을 수행하고 있습니다. 목표는 UI 스레드를 차단하지 않는 것입니다. 요청하는 모든 방법은 요청하는 스레드를 차단합니다. 언급 한 Microsoft 예제는 여러 요청을 시도하지만 여전히 요청에 대한 작업 (배경 스레드)을 작성하고 있습니다.
tronman

3
분명히 말하면, 이것은 100 % 동기 / 차단 코드입니다. 비동기를 사용하려면, WebRequest.GetResponseAsync()그리고 StreamReader.ReadToEndAync()필요가 사용 기다려온 할 수 있습니다.
Richard Szalay

4
@tronman 비동기식 등가물을 사용할 수있을 때 작업에서 차단 방법을 실행하는 것은 매우 권장되지 않는 안티 패턴입니다. 호출 스레드의 차단을 해제하는 동안 IO 완료 포트를 사용하여 비동기를 달성하는 대신 작업을 다른 스레드로 이동하기 때문에 웹 호스팅 시나리오의 규모에 영향을 미치지 않습니다.
Richard Szalay

3
public void GetResponseAsync (HttpWebRequest request, Action<HttpWebResponse> gotResponse)
    {
        if (request != null) { 
            request.BeginGetRequestStream ((r) => {
                try { // there's a try/catch here because execution path is different from invokation one, exception here may cause a crash
                    HttpWebResponse response = request.EndGetResponse (r);
                    if (gotResponse != null) 
                        gotResponse (response);
                } catch (Exception x) {
                    Console.WriteLine ("Unable to get response for '" + request.RequestUri + "' Err: " + x);
                }
            }, null);
        } 
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.