45

I have some existing code which I am porting to Windows 8 WinRT. The code fetches data from URL, asynchronously invoking a passed delegate:

private void RequestData(string uri, Action<string> action)
{
  var client = new WebClient();
  client.DownloadStringCompleted += (s,e) => action(e.Result);
  client.DownloadStringAsync(new Uri(uri));
}

Converting to WinRT requires the use of HttpClient and asynchronous methods. I've read a few tutorials on async / await, but am a bit baffled. How can I change the method above, but maintain the method signature in order to avoid changing much more of my code?

ColinE
  • 68,894
  • 15
  • 164
  • 232

6 Answers6

101
private async void RequestData(string uri, Action<string> action)
{
    var client = new WebClient();
    string data = await client.DownloadStringTaskAsync(uri);
    action(data);
}

See: http://msdn.microsoft.com/en-us/library/hh194294.aspx

Cole Cameron
  • 2,213
  • 1
  • 13
  • 12
  • 1
    The only answer of 5 that shows the whole method and keeps the same signature, so the only one that answers the question. :) – James Manning Nov 06 '12 at 02:32
  • Thanks - I just knew that it would be something really simple, but couldn't quite get my head around it! – ColinE Nov 06 '12 at 06:18
  • 7
    Just a quick note: Unless you're doing a fire and forget request, change the return type to Task, otherwise the calling thread will just continue the execution regardless if you await RequestData or not – João Nogueira Sep 25 '13 at 17:29
  • 5
    It is better to use private async Task instead of async void – Tony Jun 20 '14 at 00:24
  • 3
    Note: the important thing not to miss here is the different method being called : `DownloadString*TASK*Async` vs `DownloadStringAsync`. Because backwards compatibility needed to be retained, a new method needed to be added to the class – Simon_Weaver Nov 05 '14 at 21:54
  • `DownloadStringTaskAsync` is available since .NET 4.5.. Can I achieve this on .NET 4 ? – KeyBored Jul 01 '17 at 14:44
  • 1
    @KeyBored Depending on your exact use case, you can try one of the approaches [here](https://dotnetfiddle.net/vHkRay) – Cole Cameron Jul 05 '17 at 14:52
  • @ColeCameron Thanks alot .. a late Thank You is better than never :) – KeyBored Jul 26 '17 at 12:02
  • The `async void` return is absolutely wrong for this. – bommelding Jun 20 '18 at 07:23
  • @bommelding without more context from the question it's hard to say what's right and wrong for this user's exact use case. Generally, yes, `async Task` is preferrable to `async void`, but I opted to try to keep as much the same from the original code snippet as possible (including the "fire and forget" behavior). – Cole Cameron Jun 07 '19 at 20:12
11

How can I change the method above, but maintain the method signature in order to avoid changing much more of my code?

The best answer is "you don't". If you use async, then use it all the way down.

private async Task<string> RequestData(string uri)
{
  using (var client = new HttpClient())
  {
    return await client.GetStringAsync(uri);
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • missing async modifier on method RequestData? :) – James Manning Nov 06 '12 at 02:28
  • FWIW, in terms of keeping a "pass a callback" signature, Cole's answer seems reasonable IMHO - it's still async, uses await, and invokes the action on the 'right' thread (assuming a synchronization context is in place) AFAICT? I'll freely admit that passing callbacks like this isn't as nice as having the 'looks synchronous' style of 'normal' async/await code, but if you have existing code accepting callbacks, it seems reasonable to still accept them? Of course, "async void" is generally a bad idea, so maybe I'm forgetting a problem case here. :) – James Manning Nov 06 '12 at 02:37
  • Thanks Stephen, but I am migrating code, so minimising changes is a priority for me. This is why I want to keep the given signature. – ColinE Nov 06 '12 at 06:19
  • 2
    There are a few problems with `async void`. Unit testing, for one. But most applicable to this case is how it handles errors: any problems with the HTTP download will throw an exception directly on the `SynchronizationContext`, whereas `async Task` permits a more natural handling of exceptions (thrown from the `await`). – Stephen Cleary Nov 06 '12 at 15:47
8

Following this example, you first create the async task wtih, then get its result using await:

Task<string> downloadStringTask = client.DownloadStringTaskAsync(new Uri(uri));
string result = await downloadStringTask;
l0pan
  • 476
  • 7
  • 11
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
5
var client = new WebClient();
string page = await client.DownloadStringTaskAsync("url");

or

var client = new HttpClient();
string page = await client.GetStringAsync("url");
L.B
  • 114,136
  • 19
  • 178
  • 224
3

await the result of the HttpClient.GetStringAsync Method:

var client = new HttpClient();
action(await client.GetStringAsync(uri));
dtb
  • 213,145
  • 36
  • 401
  • 431
-1

And this piece of code is for UploadValuesAsync:

public class WebClientAdvanced : WebClient
{
    public async Task<byte[]> UploadValuesAsync(string address, string method, IDictionary<string, string> data)
    {
        var nvc = new NameValueCollection();
        foreach (var x in data) nvc.Add(x.Key, x.Value.ToStr());

        var tcs = new TaskCompletionSource<byte[]>();
        UploadValuesCompleted += (s, e) =>
        {
            if (e.Cancelled) tcs.SetCanceled();
            else if (e.Error != null) tcs.SetException(e.Error);
            else tcs.SetResult(e.Result);
        };

        UploadValuesAsync(new Uri(address), method, nvc);
        var result = await tcs.Task;
        return result;
    }
}
Srost
  • 129
  • 4