Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parallel.Invoke does not wait for async methods to complete

I have an application that pulls a fair amount of data from different sources. A local database, a networked database, and a web query. Any of these can take a few seconds to complete. So, first I decided to run these in parallel:

Parallel.Invoke(
   () => dataX = loadX(),
   () => dataY = loadY(),
   () => dataZ = loadZ()
);

As expected, all three execute in parallel, but execution on the whole block doesn't come back until the last one is done.

Next, I decided to add a spinner or "busy indicator" to the application. I don't want to block the UI thread or the spinner won't spin. So these need to be ran in async mode. But if I run all three in an async mode, then they in affect happen "synchronously", just not in the same thread as the UI. I still want them to run in parallel.

spinner.IsBusy = true;

Parallel.Invoke(
     async () => dataX = await Task.Run(() => { return loadX(); }),
     async () => dataY = await Task.Run(() => { return loadY(); }),
     async () => dataZ = await Task.Run(() => { return loadZ(); })
);

spinner.isBusy = false;

Now, the Parallel.Invoke does not wait for the methods to finish and the spinner is instantly off. Worse, dataX/Y/Z are null and exceptions occur later.

What's the proper way here? Should I use a BackgroundWorker instead? I was hoping to make use of the .NET 4.5 features.

like image 981
Paul Avatar asked Jun 19 '14 12:06

Paul


People also ask

Does parallel invoke wait for completion?

Now, the Parallel. Invoke does not wait for the methods to finish and the spinner is instantly off.

Does Async run in parallel?

Asynchronous operations in parallelThe method async. parallel() is used to run multiple asynchronous operations in parallel. The first argument to async. parallel() is a collection of the asynchronous functions to run (an array, object or other iterable).

What method of the parallel class do you use to concurrently execute multiple methods?

The Parallel Invoke method in C# is used to launch multiple tasks that are going to be executed in parallel.

Can async method have multiple awaits?

For more information, I have an async / await intro on my blog. So additionally, if a method with multiple awaits is called by a caller, the responsibility for finishing every statement of that method is with the caller.


2 Answers

It sounds like you really want something like:

spinner.IsBusy = true;
try
{
    Task t1 = Task.Run(() => dataX = loadX());
    Task t2 = Task.Run(() => dataY = loadY());
    Task t3 = Task.Run(() => dataZ = loadZ());

    await Task.WhenAll(t1, t2, t3);
}
finally
{
    spinner.IsBusy = false;
}

That way you're asynchronously waiting for all the tasks to complete (Task.WhenAll returns a task which completes when all the other tasks complete), without blocking the UI thread... whereas Parallel.Invoke (and Parallel.ForEach etc) are blocking calls, and shouldn't be used in the UI thread.

(The reason that Parallel.Invoke wasn't blocking with your async lambdas is that it was just waiting until each Action returned... which was basically when it hit the start of the await. Normally you'd want to assign an async lambda to Func<Task> or similar, in the same way that you don't want to write async void methods usually.)

like image 175
Jon Skeet Avatar answered Oct 19 '22 21:10

Jon Skeet


As you stated in your question, two of your methods query a database (one via sql, the other via azure) and the third triggers a POST request to a web service. All three of those methods are doing I/O bound work.

What happeneds when you invoke Parallel.Invoke is you basically trigger three ThreadPool threads to block and wait for I/O based operations to complete, which is pretty much a waste of resources, and will scale pretty badly if you ever need to.

Instead, you could use async apis which all three of them expose:

  1. SQL Server via Entity Framework 6 or ADO.NET
  2. Azure has async api's
  3. Web request via HttpClient.PostAsync

Lets assume the following methods:

LoadXAsync();
LoadYAsync();
LoadZAsync();

You can call them like this:

spinner.IsBusy = true;
try
{
    Task t1 = LoadXAsync();
    Task t2 = LoadYAsync();
    Task t3 = LoadZAsync();

    await Task.WhenAll(t1, t2, t3);
}
finally
{
    spinner.IsBusy = false;
}

This will have the same desired outcome. It wont freeze your UI, and it would let you save valuable resources.

like image 20
Yuval Itzchakov Avatar answered Oct 19 '22 20:10

Yuval Itzchakov