By default in a console application, async Tasks will run on the ThreadPool
, which means that multiple tasks can run in parallel on different threads (and will, if you have a multi core processor).
How can I disallow Task
s from running in parallel in my console application (without using a mutex; I don't want to have to deal with fairness issues and remembering to lock the mutex everywhere)? I'd like only one concurrent task to run at one time (i.e. I'd like the tasks to be concurrent but not parallel).
I've tried setting the max threads in the ThreadPool to 1, but the ThreadPool cannot be run with fewer threads than the CPU has cores, so that doesn't work.
You can install a single-thread synchronization context on a Console application, using the static AsyncContext
class found in the AsyncEx NuGet package by Stephen Cleary:
AsyncContext.Run(async () =>
{
await Foo();
await Task.WhenAll(items.Select(async item =>
{
var result = await Bar(item);
DoStuff(result);
}));
await FooBar();
});
The AsyncContext.Run
is a blocking call, similar to the Application.Run
method. It installs a special AsyncContextSynchronizationContext
on the current thread, much like the Application.Run
installs a WindowsFormsSynchronizationContext
on the UI thread. As long as all await
s inside the AsyncContext.Run
delegate have the default configuration, meaning no ConfigureAwait(false)
is attached to them, all continuations after each and every await
will run on the main thread of the console application. Your code will be single-threaded. Any built-in asynchronous methods might still use ThreadPool
threads under the hood to do their work, but your code won't observe these other threads, and won't be affected by them.
The AsyncContext.Run
completes when the asynchronous action
delegate completes, along with any async void
methods that might have been invoked inside the delegate. On the contrary any pending fire-and-forget tasks don't prevent the AsyncContext.Run
from completing, and are left behind in a forever-incomplete state. After the completion of the AsyncContext.Run
, the AsyncContextSynchronizationContext
is uninstalled. The SynchronizationContext.Current
reverts to its original state.
Be careful not to use .Wait()
or .Result
inside the delegate, otherwise your program will deadlock. If you block the one and only thread that executes your code, your program will grind to a halt.