You have built a complex calculation algorithm. It takes quite some time to complete and you want to make sure that your application remains responsive. What do you do?
The answer is C. However, can someone please explain why A) is incorrect? Because the question does not say that the complex algorithm is CPU bound. If it was CPU bound, then we will have to use Tasks (a reasoning I don't understand very well too, though I know that tasks do help cause the current thread to get suspended until they complete). Additionally, please explain how to decide when to use async/await, and use Tasks.
What do you do?
A. Use async/await.
B. Run the code synchronously.
C. Use Task.Run.
D. Use a BackgroundWorker.
The async/await
feature in C# 5.0 was implemented in order to make asynchronous code "as easy" as writing synchronous code. If you look into WinAPI, you'll see that almost all async endpoints expose a fully async api, meaning there is no thread. Looking further you'll notice that those same endpoints are doing I/O bound operations.
Assuming your algorithm is CPU bound, and is written in a manner which allows effective computation across multiple processors (for example, your algorithm doesn't have any shared state which needs synchronization), you can take advantage of the Task Parallel Library introduced in .NET 4.0. The TPL provides an abstraction over the ThreadPool, which in turn attempts to offload work evenly in a multiprocessor environment.
The await
keyword can be used on any awaitable object, meaning that object implements the GetAwaiter
method. You would want to use await
when you are using an awaitable and want execution to resume when that awaitable is done doing its work. A Task
implements the awaitable pattern and is used to describe a unit of work which will be completed in the future. You can use Task.Run
and await
, when you want to offload a unit of work on a worker thread and you want to return control to the calling method until that work is done.
Another way of dispatching CPU bound work across multiple processors is to use the Parallel
class which exposes Parallel.For
and Parallel.ForEach
.
On a side note, a BackgroundWorker
can also be used to offload work on a background thread, if thats what the person asking the question was after. It is recommended to use the TPL since .NET 4.0 was released.
To conclude, using the Task Parallel Library is the recommended way to offload work to background threads. You may use them in combination with the Parallel
library to maximize the parallelism of your algorithm. After doing so, test your code to make sure the overhead of using multiple threads doesn't outweigh the time it takes to run your algo synchronously.
Edit
As Stephan mentioned in the comment, you may combine both Parallel.ForEach
and Task.Run
to offload a parallel loop inside a background thread:
var task = Task.Run(() => Parallel.ForEach(.. //Loop here));
await Task.Run(() => Parallel.ForEach(...))
will do parallel work on background threads that the UI treats asynchronously. - Stephen Cleary
I suppose "calculation" implies CPU bound algorithm in the absence of other information. If the algorithm is IO bound async/await is not only acceptable but the correct answer.
Task.Run
, the delegate passed to it will likely execute on a thread other than the UI thread, so your UI will remain responsive in any case where you don't block on the task's completion via Wait()
or Result
. How you choose to handle task's completion and communicate with the thread that called Task.Run
in the first place, however, is up to you: you can use await
, or you can use task continuations with the appropriate TaskScheduler
, or you can explicitly capture the original SynchronizationContext and post back to it from within your delegate, - Kirill ShlenskiyDispatcher.Invoke
or Control.Invoke
, or you can roll your own AsyncEnumerator (blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx). My point is, there's about a million ways to do it, and async/await
just happens to be one of them, even if it is the first thing that comes to everyone's mind (which is quite understandable). - Kirill Shlenskiy
I believe the question assumes you have an algorithm that has been programmed synchronously. The question mentions that it is a 'calculation' which vaguely implies that it is CPU-bound. It also very vaguely implies that the algorithm is written synchronously. E.g:
public int CalculateStuff() {
....
}
What I would consider is creating an asynchronous counterpart to this method:
public async Task<int> CalculateStuffAsync() {
return await Task.Run(() => CalculateStuff());
}
Then in your user interface:
LoadingIndicator.IsEnabled = true;
ResultTextBox.Text = await CalculateStuffAsync();
LoadingIndicator.IsEnabled = false;
Hence the correct answer would probably be both A and C. In any case the question is too vague to draw any solid conclusion.
xxxAsync
wrappers for CPU-bound work is poor design. It's not like the caller can't wrap CalculateStuff
in Task.Run
if it needs to. Also consider the following: CalculateStuffAsync().Wait()
. Without ConfigureAwait(false)
you've just introduced a deadlock bug that wasn't there until you wrote your async
method, for pretty much zero benefit. - Kirill ShlenskiySynchronizationContext
(i.e. Windows Forms/WPF) and see what happens. This is due to the fact that your await
needs to transition to the original SynchronizationContext
before the result of the async operation can be returned. If you block the thread via Task.Wait
, that transition will never occur because the thread will be busy waiting for the task (which will never complete). Checkmate. Take out async/await
and just return the task directly, and it's solved. - Kirill ShlenskiyConfigureAwait(false)
obviously) - Kirill Shlenskiy