10

I am trying to wrap my head around async/await and wanted to know if this is the proper use of the Task.WhenAll method:

public class AsyncLib
{
    public async Task<IEnumerable<string>> DoIt()
    {
        var urls = new string[] { "http://www.msn.com", "http://www.google.com" };

        var tasks = urls.Select(x => this.GetUrlContents(x));

        var results = await Task.WhenAll(tasks);

        return results.Select(x => x);
    }

    public async Task<string> GetUrlContents(string url)
    {
        using (var client = new WebClient())
        {
            return await client.DownloadStringTaskAsync(url);
        }
    }
}

Main

This is the calling console application.

class Program
{
    static void Main(string[] args)
    {
        var lib = new AsyncLib();
        foreach(var item in lib.DoIt().Result)
        {
            Console.WriteLine(item.Length);
        }
        Console.Read();

    }
}
Sam
  • 15,336
  • 25
  • 85
  • 148
  • @SimonWhitehead - I am new to async/await, so just trying to wrap my head around it. Proper I guess would mean "is there a better way?" – Sam May 13 '14 at 01:09
  • 1
    This is generally how I do / have seen it implemented. You need to wait for a set of tasks to complete asynchronously.. `WhenAll` is generally how you do it (that I know of). – Simon Whitehead May 13 '14 at 01:14
  • 2
    In this case, the return type of `Task.WhenAll` is `string[]` which implements `IEnumerable`. `results.Select(x => x)` does nothing but adding overhead. And this is not related to `async-await`. It's just basic `LINQ` and type system awareness. – Paulo Morgado May 13 '14 at 11:20
  • You should call your method `DoItAsync` as per the [guidelines](http://msdn.microsoft.com/library/vstudio/hh191443.aspx "Asynchronous Programming with Async and Await (C# and Visual Basic)"). – Paulo Morgado May 13 '14 at 11:23
  • Read the articles on [my curation](http://curah.microsoft.com/45553/asyncawait-general "async-await General") to learn more. – Paulo Morgado May 13 '14 at 11:23

1 Answers1

17

The problem with your current code is that you won't be able to handle individual exceptions, if more than one task throws.

If this is a concern, then with the following approach, you can handle them:

public async Task<Task<string>[]> DoIt()
{
    var urls = new string[] { "http://www.msn.com", "http://www.google.com" };

    var tasks = urls.Select(x => this.GetUrlContents(x)).ToArray();

    await Task.WhenAll(tasks);

    return tasks;
}

// ...

static void Main(string[] args)
{
    var lib = new AsyncLib();
    foreach(var item in lib.DoIt().Result)
    {
        Console.WriteLine(item.Result.Length);
    }
    Console.Read();

}

Note I use ToArray() to avoid evaluating the enumerable and starting the tasks for more than once (as LINQ is lazy-evaluated).

Updated, now you can further optimize DoIt by eliminating async/await:

public Task<Task<string>[]> DoIt()
{
    var urls = new string[] { "http://www.msn.com", "http://www.google.com" };

    var tasks = urls.Select(x => this.GetUrlContents(x)).ToArray();

    return Task.Factory.ContinueWhenAll(
        tasks, 
        _ => tasks, 
        CancellationToken.None, 
        TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

However, if you do so, be aware of the change in the exception propagation behavior.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486