1

I'm a new in the async world. Regarding your static method (from https://stackoverflow.com/a/25733275/1596974):

static async Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks, TimeSpan timeout)
{
    var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult));
    var completedTasks = 
        (await Task.WhenAll(tasks.Select(task => Task.WhenAny(task, timeoutTask)))).
        Where(task => task != timeoutTask);
    return await Task.WhenAll(completedTasks);
}

How should I use it in order to retrieve the results of those tasks? Just to be clear, what I need to achieve here is basically this:

  • For each task I'm calling to several shipping providers in order to get the different shipping rates from them.
  • Aggregate the response from all the shipping providers into a big list of shipping rates. Sometimes one (or more) of the shipping providers can be down. So, I need to retrieve the shipping rates from the tasks that completed successfully and just skip the ones that failed. I hope I was clear enough.
msola
  • 119
  • 1
  • 7

2 Answers2

2

Ok, I ended up with this code that worked pretty well:

var providers = GetShippingProviders().ToList();
var tasks = new Task<Task>[providers.Count];
var timeout = TimeSpan.FromMilliseconds(10000);    

try
{
    var shippingRates = new List<IShippingRate>();

    for (var i = 0; i < tasks.Length; i++)
    {
        var provider = providers[i];
        tasks[i] = Task.WhenAny(Task.Run(() => provider.GetShippingRates(origin, destination, weight)), Task.Delay(timeout));
    }

    Task.WaitAll(tasks);

    foreach (var tasksResult in tasks.Select(x => x.Result).Where(x => x.Status == TaskStatus.RanToCompletion))
    {
        var shippingRatesResult = tasksResult as Task<List<IShippingRate>>;
        if (shippingRatesResult != null)
            shippingRates.AddRange(shippingRatesResult.Result.ToList());
    }
}
catch (AggregateException ae)
{
    Log.Error("An exception occurred when retrieving the shipping Rates.", ae.Flatten());        
}

All the tasks that completed successfully are processed. The ones that failed can be just skipped. There is a way to add some code using the "Task.ContinueWith(...)" so the faulted tasks can be catched for logging the exceptions as well.

msola
  • 119
  • 1
  • 7
1

For the ones that fail, do they throw an exception? Or do they just hang for a long time?

I might do something like this:

var shippingProviderRateTasks = ...;
var results = ...;

foreach (var task in shippingProviderRateTasks) {
    try {
        results.Add(await task);
    } catch (Exception e) {
        // log the error here, if you want, and skip this provider
    }
}

One of the primary reasons to use Task.WhenAll is to catch an exception when it occurs, and not later on. If you want to swallow all the exceptions and essentially ignore those errors, you might as well just await each of them one at a time - it shouldn't be any slower.

libertyernie
  • 2,590
  • 1
  • 17
  • 13