Single-threaded async/await in C# console app

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 Tasks 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.

  • 3

    You can use a semaphore…. Also why are you using tasks if you don’t want them?

    – 

  • 5

    Your question is based on two incorrect assumptions. The default SynchronizationContext in console applications is null, not the ThreadPool, as you can easily verify by printing the SynchronizationContext.Current. Also the presence or absence of a SynchronizationContext makes no difference regarding the concurrent behavior of tasks. If you start a second task without awaiting the first task, you have concurrency no matter what.

    – 

  • 4

    The title of the question talks about threads, but the body talks about concurrency. Disallowing more than one tasks to run at a time is just another way of saying “disallowing concurrency”. Tasks typically don’t run on threads, so you can have concurrency even with zero threads.

    – 

  • 2

    Sounds OK. So, as I said: put extra effort in only using I/O Tasks and you should be good. For the distinction maybe have a look into Stephen Cleary’s blog.

    – 




  • 2

    That sounds like: I want to go swimming but not getting wet

    – 

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 SynchronizationContext on the current thread, much like the Application.Run installs a WindowsFormsSynchronizationContext on the UI thread. As long as you await everything directly inside the AsyncContext.Run delegate, meaning without ConfigureAwait(false), 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 stuff, but your code won’t observe these other threads, and won’t be affected by them.

Be careful not to use .Wait() or .Result anywhere, otherwise your code will deadlock. If you block the single thread that executes your code, your program will grind to a halt.

Leave a Comment