0

I have bunch of async methods, which I invoke from Dispatcher. The methods does not perform any work in the background, they just waits for some I/O operations, or wait for response from the webserver.

async Task FetchAsync()
{
    // Prepare request in UI thread
    var response = await new WebClient().DownloadDataTaskAsync();
    // Process response in UI thread
}

now, I want to perform load tests, by calling multiple FetchAsync() in parallel with some max degree of parallelism.

My first attempt was using Paralell.Foreach(), but id does not work well with async/await.

var option = new ParallelOptions {MaxDegreeOfParallelism = 10};
Parallel.ForEach(UnitsOfWork, uow => uow.FetchAsync().Wait());

I've been looking at reactive extensions, but I'm still not able to take advantage of Dispatcher and async/await.

My goal is to not create separate thread for each FetchAsync(). Can you give me some hints how to do it?

David Pine
  • 23,787
  • 10
  • 79
  • 107
Liero
  • 25,216
  • 29
  • 151
  • 297
  • Don't mix `Parallel.ForEach` with `async/await` http://stackoverflow.com/a/11565317/585968 as you have discovered –  Jul 22 '16 at 08:08
  • You cannot run things in parallel without separate threads. Take a look at [concurrency vs parallelism what is the difference](http://stackoverflow.com/questions/1050222/concurrency-vs-parallelism-what-is-the-difference) – Cameron MacFarland Jul 22 '16 at 08:15
  • @AntP: you've removed my EDIT because "no need to edit question with asnwers", but in my edit was important modification of the answer. Please roll it back! – Liero Jul 22 '16 at 08:54
  • @Liero if it's different enough to be a separate answer, it should be posted as a separate answer (not inside the question). From what I saw, though, it was a pretty narrow change that only applies to this specific example and won't be an important distinction for future readers. – Ant P Jul 22 '16 at 08:56

2 Answers2

2

Just call Fetchasync without awaiting each call and then use Task.WhenAll to await all of them together.

var tasks = new List<Task>();
var max = 10;
for(int i = 0; i < max; i++)
{
    tasks.Add(FetchAsync());
}

await Task.WhenAll(tasks);
wertzui
  • 5,148
  • 3
  • 31
  • 51
  • when I do `await Task.WhenAll()`, then FetchAsync will be still invoked in UI thread? – Liero Jul 22 '16 at 08:12
  • @Liero FetchAsync is invoked when FetchAsync is called, not when it's awaited, but that's details. – J. Steen Jul 22 '16 at 08:12
  • Ok, now I need to add MaxDegreeOfParallelism equivalent :) Of course I can implement some logic manually using Task.WhenAny(), but isn't there something build it, that could help me? – Liero Jul 22 '16 at 08:18
  • Remember: There are no threads in this case. Waiting on the UI operations works with handles that register the continuation in the `ThreadPool`. The `ThreadPool` itself has a limit of concurrently active threads that depend on your system and your usage of the pool. So there is no real "max parallelism". If you want to limit the amount of concurrent requests, you'd have to implement the logic for that yourself. – Nitram Jul 22 '16 at 08:30
  • 2
    You don't need `if(tasks.Any())` - if you call `Task.WhenAll` with an empty set of tasks it will just complete immediately. – Ant P Jul 22 '16 at 08:49
  • @Liero: You can limit asynchronous concurrency with `SemaphoreSlim`. There isn't a built-in setting like `MaxDegreeOfParallelism`, because the work does not execute *within* `WhenAny`, like it does with `Parallel`. – Stephen Cleary Jul 22 '16 at 11:58
0

Here is a generic reusable solution to your question that you can reuse not only with your FetchAsync method but for any async method that has the same signature. The api includes real time concurrent throttling support as well:

Parameters are self explanatory: totalRequestCount: is how many async requests (FatchAsync calls) you want to do in total, async processor is the FetchAsync method itself, maxDegreeOfParallelism is the optional nullable parameter. If you want real time concurrent throttling with max number of concurrent async requests, set it, otherwise not.

public static Task ForEachAsync(
        int totalRequestCount,
        Func<Task> asyncProcessor,
        int? maxDegreeOfParallelism = null)
    {
        IEnumerable<Task> tasks;

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

            tasks = Enumerable.Range(0, totalRequestCount).Select(async requestNumber =>
            {
                await throttler.WaitAsync();
                try
                {
                    await asyncProcessor().ConfigureAwait(false);
                }
                finally
                {
                    throttler.Release();
                }
            });
        }
        else
        {
            tasks = Enumerable.Range(0, totalRequestCount).Select(requestNumber => asyncProcessor());
        }

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