2

I have a bunch of slow functions that are essentially this:

private async Task<List<string>> DownloadSomething()
{
    var request = System.Net.WebRequest.Create("https://valid.url");

    ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        //read stream and return data
    }

}

This works nicely and asynchronously except for the call to WebRequest.Create - this single line freezes the UI thread for several seconds which sort of ruins the purpose of async/await.

I already have this code written using BackgroundWorkers, which works perfectly and never freezes the UI.
Still, what is the correct, idiomatic way to create a web request with respect to async/await? Or maybe there is another class that should be used?

I've seen this nice answer about asyncifying a WebRequest, but even there the object itself is created synchronously.

Community
  • 1
  • 1
GSerg
  • 76,472
  • 17
  • 159
  • 346

2 Answers2

6

Interestingly, I'm not seeing a blocking delay with WebRequest.Create or HttpClient.PostAsync. It might be something to do with DNS resolution or proxy configuration, although I'd expect these operations to be implemented internally as asynchronous, too.

Anyway, as a workaround you can start the request on a pool thread, although this is not something I'd normally do:

private async Task<List<string>> DownloadSomething()
{
    var request = await Task.Run(() => {
        // WebRequest.Create freezes??
        return System.Net.WebRequest.Create("https://valid.url");
    });

    // ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        //read stream and return data
    }
}

That would keep the UI responsive, but it might be difficult to cancel it if user wants to stop the operation. That's because you need to already have a WebRequest instance to be able to call Abort on it.

Using HttpClient, cancellation would be possible, something like this:

private async Task<List<string>> DownloadSomething(CancellationToken token)
{
    var httpClient = new HttpClient();

    var response = await Task.Run(async () => {
        return await httpClient.PostAsync("https://valid.url", token);
    }, token);

    // ...
}

With HttpClient, you can also register a httpClient.CancelPendingRequests() callback on the cancellation token, like this.


[UPDATE] Based on the comments: in your original case (before introducing Task.Run) you probably did not need the IProgress<I> pattern. As long as DownloadSomething() was called on the UI thread, every execution step after each await inside DownloadSomething would be resumed on the same UI thread, so you could just update the UI directly in between awaits.

Now, to run the whole DownloadSomething() via Task.Run on a pool thread, you would have to pass an instance of IProgress<I> into it, e.g.:

private async Task<List<string>> DownloadSomething(
    string url, 
    IProgress<int> progress, 
    CancellationToken token)
{
    var request = System.Net.WebRequest.Create(url);

    // ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        // read stream and return data
        progress.Report(...); // report progress  
    }
}

// ...

// Calling DownloadSomething from the UI thread via Task.Run:

var progressIndicator = new Progress<int>(ReportProgress);
var cts = new CancellationTokenSource(30000); // cancel in 30s (optional)
var url = "https://valid.url";
var result = await Task.Run(() => 
    DownloadSomething(url, progressIndicator, cts.Token), cts.Token);
// the "result" type is deduced to "List<string>" by the compiler 

Note, because DownloadSomething is an async method itself, it is now run as a nested task, which Task.Run transparently unwraps for you. More on this: Task.Run vs Task.Factory.StartNew.

Also check out: Enabling Progress and Cancellation in Async APIs.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Works this way. However, I tried to adapt this by `Task.Run`ning the whole thing and not just one bit inside a function (so that there is just one additional thread that does its own `await` business, and the guts of each function do not care what thread they are on). That did not work because I have repoting progress via `IProgress` where I access UI elements, and that fails with cross-thread access. Is it possible to somehow make the outermost lambda passed to `Task.Run()` use the correct synch context, or will I have to put `Invoke`s in my `Report` method? – GSerg Jan 09 '14 at 00:19
  • This is essentially what I had after adopting your original answer. Alas, passing the same cancellation token this way does not magically marshal calls to the UI thread. Still fails inside `progressIndicator.Report()` because of cross-thread access to UI. I've fixed it by checking `InvokeRequired` inside the reporting function and `Invoke`ing as required, but this feels a shame (compared to background worker where this happens automatically) and a leaky abstraction. I can leave it as is, but I'd really love to learn how to make it work without breaking the abstraction. – GSerg Jan 09 '14 at 07:55
  • @GSerg, that's odd. Are you actually creating `Progress` on the *UI* thread, before passing it around? The `Progress` constructor captures the current synchronization context of the thread it gets created on, so the progress callbacks are *guaranteed* to be called on the same synchronization context (that of the UI thread in your case). Try the following right before you create an instance of `Progress`: `MessageBox.Show(SynchronizationContext.Current.GetType().Name)`. What do you see? – noseratio Jan 09 '14 at 08:09
  • 2
    @GSerg, don't inherit, just use the stock class and give it your own progress callback: `new Progress(Report)`. It *will* call `Report` on the correct context. The full example is available by link at the end of my answer. – noseratio Jan 09 '14 at 08:47
  • Yes, I was an idiot because I implemented my own `IProgress` without inheriting from `Progress`. Inheriting and reporting from the `Progress.ProgressChanged` event handler works properly. (I deleted my original comment because I was an idiot there too, overriding `OnReport` instead.) – GSerg Jan 09 '14 at 08:47
  • 2
    I think the lambda in `Task.Run()` should *return* the created request, not set a variable. – svick Jan 09 '14 at 12:21
  • @svick, I agree. We did not address it as we moved on with the second option (the whole logic inside `Task.Run`). – noseratio Jan 09 '14 at 12:31
0

I think you need to use HttpClient.GetAsync() which returns a task from an HTTP request.

http://msdn.microsoft.com/en-us/library/hh158912(v=vs.110).aspx

It may depend a bit on what you want to return, but the HttpClient has a whole bunch of async methods for requests.

dougajmcdonald
  • 19,231
  • 12
  • 56
  • 89
  • 1
    Unfortunately this gives the same hang on the `await PostAsync` line. The hang stops before the await finishes, so I assume inside `HttpClient` there is a `HttpWebRequest` being synchronously created and asynchronously read, which is exactly what I'm doing manually. – GSerg Jan 08 '14 at 22:14
  • Ah I see, good point. I guess if you're dependent on the result of the request you'll always need to wait until it's finished. You can continue to process other stuff, but you won't be able to interact with the result. As a side point, there is a really nice InCompletionOrder() function if you need to make multiple requests and process the data in the order they return. https://gist.github.com/mswietlicki/6112628 – dougajmcdonald Jan 09 '14 at 08:25