161

How can I use HttpWebRequest (.NET, C#) asynchronously?

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
Jason
  • 16,739
  • 23
  • 87
  • 137
  • 1
    use async http://msdn.microsoft.com/en-us/library/system.net.webrequest.endgetrequeststream.aspx – Raj Kaimal Mar 26 '10 at 22:55
  • 1
    for a moment, I wondered if you were trying to comment on a recursive thread? – Kyle May 20 '10 at 03:17
  • You can also see the following, for a pretty complete example of doing what Jason is asking: http://stuff.seans.com/2009/01/05/using-httpwebrequest-for-asynchronous-downloads/ Sean – Sean Sexton Apr 08 '09 at 19:43
  • 2
    Check out this article on Developer Fusion: http://www.developerfusion.com/code/4654/asynchronous-httpwebrequest/ –  Oct 14 '08 at 19:24

10 Answers10

127

Use HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

The callback function is called when the asynchronous operation is complete. You need to at least call EndGetResponse() from this function.

Jon B
  • 51,025
  • 31
  • 133
  • 161
  • 17
    BeginGetResponse is not that useful for async usage. It seems to block while trying to contact the resource. Try unplugging your network cable or giving it a malformed uri, and then running this code. Instead you probably need to run GetResponse on a second thread you provide. – Ash Oct 21 '12 at 03:52
  • 2
    @AshleyHenderson - Could you please provide me a sample? – Tohid Nov 14 '12 at 14:28
  • 1
    @Tohid [here is a full class with sample](http://stackoverflow.com/questions/12224602/a-method-for-making-http-requests-on-unity-ios/12606963#12606963) I've used with Unity3D. – cregox May 27 '13 at 23:17
  • 3
    You should add `webRequest.Proxy = null` to speed up the request dramatically. – Trontor Oct 30 '13 at 10:55
  • C# throws an error telling me that this is an obsolete class – AleX_ Aug 03 '17 at 14:29
72

By far the easiest way is by using TaskFactory.FromAsync from the TPL. It's literally a couple of lines of code when used in conjunction with the new async/await keywords:

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

If you can't use the C#5 compiler then the above can be accomplished using the Task.ContinueWith method:

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });
Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56
67

Considering the answer:

HttpWebRequest webRequest;

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

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

You could send the request pointer or any other object like this:

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

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

Greetings

xlarsx
  • 981
  • 1
  • 9
  • 8
  • 7
    +1 for the option that doesn't over-scope the 'request' variable, but you could have made a cast instead of using "as" keyword. An InvalidCastException would be thrown instead of a confuse NullReferenceException – Davi Fiamenghi Jun 30 '12 at 20:38
64

Everyone so far has been wrong, because BeginGetResponse() does some work on the current thread. From the documentation:

The BeginGetResponse method requires some synchronous setup tasks to complete (DNS resolution, proxy detection, and TCP socket connection, for example) before this method becomes asynchronous. As a result, this method should never be called on a user interface (UI) thread because it might take considerable time (up to several minutes depending on network settings) to complete the initial synchronous setup tasks before an exception for an error is thrown or the method succeeds.

So to do this right:

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

You can then do what you need to with the response. For example:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
Isak
  • 1,591
  • 1
  • 17
  • 23
  • 2
    Could you not just call the GetResponseAsync method of the HttpWebRequest using await (assuming you made your function async)? I've very new to C# so this may be complete jibberish... – Brad Feb 20 '13 at 23:17
  • GetResponseAsync looks good, though you'll need .NET 4.5 (currently beta). – Isak Feb 21 '13 at 17:23
  • 16
    Jesus. That is some ugly code. Why can't async code be readable? – John Shedletsky May 14 '13 at 01:25
  • Why do you need request.BeginGetResponse()? Why wrapperAction.BeginInvoke() does not suffice? – Igor Gatis Aug 21 '15 at 00:41
  • 2
    @Gatis There are two levels of asynchronous calls - wrapperAction.BeginInvoke() is the first asynchronous call to the lambda expression which calls request.BeginGetResponse(), which is the second asynchronous call. As Isak points out, BeginGetResponse() requires some synchronous setup, which is why he wraps it in an additional asynchronous call. – walkingTarget Nov 11 '15 at 17:12
9
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);
}
dragansr
  • 417
  • 6
  • 8
7

I ended up using BackgroundWorker, it is definitely asynchronous unlike some of the above solutions, it handles returning to the GUI thread for you, and it is very easy to understand.

It is also very easy to handle exceptions, as they end up in the RunWorkerCompleted method, but make sure you read this: Unhandled exceptions in BackgroundWorker

I used WebClient but obviously you could use HttpWebRequest.GetResponse if you wanted.

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();
Community
  • 1
  • 1
eggbert
  • 3,105
  • 5
  • 30
  • 39
6

.NET has changed since many of these answers were posted, and I'd like to provide a more up-to-date answer. Use an async method to start a Task that will run on a background thread:

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

To use the async method:

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

Update:

This solution does not work for UWP apps which use WebRequest.GetResponseAsync() instead of WebRequest.GetResponse(), and it does not call the Dispose() methods where appropriate. @dragansr has a good alternative solution that addresses these issues.

tronman
  • 9,862
  • 10
  • 46
  • 61
  • 1
    Thank you ! Have been trying to find an async example, lots of examples using old approach which is over complex. – WDUK Nov 24 '17 at 05:40
  • Will this not block a thread for each response? it seems quite a bit different to e.g. https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-wrap-eap-patterns-in-a-task – Pete Kirkham Jan 11 '18 at 15:11
  • @PeteKirkham A background thread is doing the request, not the UI thread. The goal is to avoid blocking the UI thread. Any method you choose to make a request will block the thread making the request. The Microsoft example you refer to is trying to make multiple requests, but they are still creating a Task (a background thread) for the requests. – tronman Jan 12 '18 at 16:07
  • That may be your goal, it's not mine and there's no way to say it was the OP's. For such a simple goal, it may suffice, but for say a web robot making many requests it will take on thread per request, which is very expensive. – Pete Kirkham Jan 12 '18 at 16:50
  • @PeteKirkham I may be mistaken, but I believe the WebClient class from your link creates a thread for each request. – tronman Jan 17 '18 at 17:21
  • Maybe with six requests, but if you make it do more requests it can have e.g. 146 requests in progress on 68 threads (68 seems to be the limit for the number of threads it will spawn for more and more requests). – Pete Kirkham Jan 17 '18 at 21:09
  • 3
    To be clear, this is 100% synchronous/blocking code. To use async, `WebRequest.GetResponseAsync()` and `StreamReader.ReadToEndAync()` need to be used and awaited. – Richard Szalay Mar 11 '18 at 22:02
  • @RichardSzalay, running the blocking code in a Task makes it async, but I did add an update indicating the limitations of the solution. – tronman Mar 13 '18 at 15:54
  • 4
    @tronman Running blocking methods in a Task when async equivalents are available is a highly discouraged anti-pattern. While it does unblock the calling thread, it does nothing for scale for web hosting scenarios since you're just moving the work to another thread rather than using IO completion ports to achieve the asynchrony. – Richard Szalay Mar 13 '18 at 19:17
  • @RichardSzalay Agreed. However, if the async methods like GetResponseAsync are not available, the above solution would be an acceptable alternative. – tronman Mar 15 '18 at 16:01
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);
        } 
    }
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
0

Follow up to the @Isak 's answer, which is very good. Nonetheless it's biggest flaw is that it will only call the responseAction if the response has status 200-299. The best way to fix this is:

private void DoWithResponseAsync(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            }
            catch (WebException ex)
            {
                // It needs to be done like this in order to read responses with error status:
                response = ex.Response as HttpWebResponse;
            }
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

And then as @Isak follows:

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

I've been using this for async UWR, hopefully it helps someone

    string uri = "http://some.place.online";

    using (UnityWebRequest uwr = UnityWebRequest.Get(uri))
    {
        var asyncOp = uwr.SendWebRequest();
        while (asyncOp.isDone == false) await Task.Delay(1000 / 30); // 30 hertz

        if(uwr.result == UnityWebRequest.Result.Success) return uwr.downloadHandler.text;
        Debug.LogError(uwr.error);
    }
Jacksonkr
  • 31,583
  • 39
  • 180
  • 284