6

Using the async/await model, I have a method which makes 3 different calls to a web service and then returns the union of the results.

var result1 = await myService.GetData(source1);
var result2 = await myService.GetData(source2);
var result3 = await myService.GetData(source3);

allResults = Union(result1, result2, result3);

Using typical await, these 3 calls will execute synchronously wrt each other. How would I go about letting them execute concurrently and join the results as they complete?

earthling
  • 5,084
  • 9
  • 46
  • 90
  • What is the return type of `GetData` method? – Yurii Mar 01 '14 at 17:03
  • Doesn't matter. Stream, string, byte[]... – earthling Mar 01 '14 at 17:04
  • 1
    Don't await them immediately, store the returned values in an array or list and use `await Task.WhenAll(...);`. – Dirk Mar 01 '14 at 17:04
  • Yes just found this article that talks a little about WhenAll http://msdn.microsoft.com/en-us/magazine/jj991977.aspx – earthling Mar 01 '14 at 17:06
  • @earthling, this is also related: [Why should I prefer single 'await Task.WhenAll' over multiple awaits?](http://stackoverflow.com/questions/18310996/why-should-i-prefer-single-await-task-whenall-over-multiple-awaits) – avo Mar 03 '14 at 12:20

3 Answers3

10

How would I go about letting them execute in parallel and join the results as they complete?

The simplest approach is just to create all the tasks and then await them:

var task1 = myService.GetData(source1);
var task2 = myService.GetData(source2);
var task3 = myService.GetData(source3);

// Now everything's started, we can await them
var result1 = await task1;
var result1 = await task2;
var result1 = await task3;

You might also consider Task.WhenAll. You need to consider the possibility that more than one task will fail... with the above code you wouldn't observe the failure of task3 for example, if task2 fails - because your async method will propagate the exception from task2 before you await task3.

I'm not suggesting a particular strategy here, because it will depend on your exact scenario. You may only care about success/failure and logging one cause of failure, in which case the above code is fine. Otherwise, you could potentially attach continuations to the original tasks to log all exceptions, for example.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks Jon. I Just came across WhenAll. I'll look into using that. Btw, I assume in the code sample you meant to remove the awaits from the method calls, correct? – earthling Mar 01 '14 at 17:08
  • 1
    If you `await` `Task.WhenAll` (the common case), it will not throw `AggregateException`. Rather, it will (re)throw *one* of the exceptions from the faulting tasks. – Stephen Cleary Mar 01 '14 at 17:17
0

You could use the Parallel class:

Parallel.Invoke(
() => result1 = myService.GetData(source1),
() => result2 = myService.GetData(source2),
() => result3 = myService.GetData(source3)
);

For more information visit: http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel(v=vs.110).aspx

alejosoft
  • 186
  • 2
  • 7
  • This won't help, GetData is an async method, so invoking it in parallel doesn't make any sense. – svick Mar 01 '14 at 19:48
0

As a more generic solution you can use the api I wrote below, it also allows you to define a real time throttling mechanism of max number of concurrent async requests.

The inputEnumerable will be the enumerable of your source and asyncProcessor is your async delegate (myservice.GetData in your example).

If the asyncProcessor - myservice.GetData - returns void or just a Task without any type, then you can simply update the api to reflect that. (just replace all Task<> references to Task)

    public static async Task<TOut[]> ForEachAsync<TIn, TOut>(
        IEnumerable<TIn> inputEnumerable,
        Func<TIn, Task<TOut>> asyncProcessor,
        int? maxDegreeOfParallelism = null)
    {
        IEnumerable<Task<TOut>> tasks;

        if (maxDegreeOfParallelism != null)
        {
            SemaphoreSlim throttler = new SemaphoreSlim(maxDegreeOfParallelism.Value, maxDegreeOfParallelism.Value);

            tasks = inputEnumerable.Select(
                async input =>
                    {
                        await throttler.WaitAsync();
                        try
                        {
                            return await asyncProcessor(input).ConfigureAwait(false);
                        }
                        finally
                        {
                            throttler.Release();
                        }
                    });
        }
        else
        {
            tasks = inputEnumerable.Select(asyncProcessor);
        }

        await Task.WhenAll(tasks);
    }
Dogu Arslan
  • 3,292
  • 24
  • 43