How to use Task.WhenAll() correctly

13,756

Solution 1

This was down to a basic lack of understanding of how async-await really works.

The inner task was returning flow to the outer task, which then finished before the await ever returned.

To achieve what I wanted, I needed to refactor as follows:

List<Task<BusRoute>> routeRetrievalTasks = new List<Task<BusRoute>>();
foreach (BusRouteIdentifier bri in stop.services)
{
    BusRouteRequest req = new BusRouteRequest(bri.id);
    routeRetrievalTasks.Add(BusDataProviderManager.DataProvider.DataBroker.getRoute(req));
}

foreach (var task in routeRetrievalTasks)
{
    var route = await task;
    this.routes.Add(route); // triggers events
}

Thanks to Dave Smits

Solution 2

I suspect the problem is your call to Task.Factory.StartNew(). I suspect you're ending up with a Task<Task>, and you're only finding out when it's effectively started the task.

Try this instead:

Func<Task> taskFunc = async () =>
{
    var route = await BusDataProviderManager.DataProvider.DataBroker.getRoute(req);

    // Add the route to our array (on UI thread as it's observed)
    await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate
    {
        this.routes.Add(route);
    });

}

Task getRouteTask = Task.Run(taskFunc);
Share:
13,756

Related videos on Youtube

Carlos P
Author by

Carlos P

Coder and mobile app developer (e.g. www.buschecker.com): C++, C# .NET, Silverlight, Objective C, Java, HTML &amp; XAML. SOreadytohelp

Updated on June 04, 2022

Comments

  • Carlos P
    Carlos P almost 2 years

    I am trying to use Task.WhenAll to await completion of multiple tasks.

    My code is below - it is supposed to launch multiple async tasks, each of which retrieves a bus route and then adds them to a local array. However, Task.WhenAll(...) returns immediately, and the count of the local routes array is zero. This seems strange, since I would expect the various await statements within each Task to mean that the flow is suspended, and the Task does not return until it's finished.

    List<Task> monitoredTasks = new List<Task>();
    foreach (BusRouteIdentifier bri in stop.services)
    {
        BusRouteRequest req = new BusRouteRequest(bri.id);
    
        // Start a new task to fetch the route for each stop
        Task getRouteTask = Task.Factory.StartNew(async () =>
        {
            var route = await BusDataProviderManager.DataProvider.DataBroker.getRoute(req);
    
                // Add the route to our array (on UI thread as it's observed)
                await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate
                {
                    this.routes.Add(route);
                });
        });
    
        // Store the task in our monitoring list
        monitoredTasks .Add(getRouteTask);
    }
    
    Debug.WriteLine("Awaiting WHENALL");
    await Task.WhenAll(monitoredTasks );
    Debug.WriteLine(string.Format("WHENALL returned (routes count is {0} ", this.routes.Count));
    
    this.OnWillEndFetchingRoutes(new EventArgs());
    

    I'm obviously doing something wrong - but what?

  • Carlos P
    Carlos P over 11 years
    Thanks. I assume that the rest of my method would continue as before, i.e. add the Task to the array of tasks, then call Task.WhenAll(...) on the array. In which case, how does this approach compare to the one in my Answer? Are there any advantages?
  • Jon Skeet
    Jon Skeet over 11 years
    @CarlosP: To be honest I don't really have enough context to comment on which is a better approach. But yes, the rest of your method would continue as before.