Search code examples
c#asynchronousasync-awaitconcurrencytask

Tasks running asynchronously even though Main method is not declared as async


I write the code bellow and forgot to declare Main method as async, so my guess was that the producer and consumer tasks should run synchronously.

using System.Collections.Concurrent;

namespace MainProgram;

public class Program
{
    public static void Main(string[] args)
    {
        using var intCollection = new BlockingCollection<int>();
        int sum = 0;

        Task.Run(() =>
        {
            Console.WriteLine("Running Producer");
            Thread.Sleep(1000);
            for (int i = 0; i < 10; i++)
            {
                intCollection.Add(i);

                Console.WriteLine(i);
            }

            intCollection.CompleteAdding();
        });

        Task.Run(() =>
        {
            Console.WriteLine("Running Consumer");
            Thread.Sleep(1000);
            while (!intCollection.IsCompleted)
            {
                sum += intCollection.Take();
                Console.WriteLine("Thingi Thingi");
            }
        });

        Console.WriteLine("aaabbb");
        Console.ReadLine();
    }
}

But the output of this code looks like this:

aaabbb
Running Producer
Running Consumer
0
Thingi Thingi
1
Thingi Thingi
2
Thingi Thingi
3
Thingi Thingi
4
Thingi Thingi
5
Thingi Thingi
6
Thingi Thingi
7
8
9
Thingi Thingi
Thingi Thingi
Thingi Thingi

This is to my surprise, because the tasks appear to run asynchronously. Can someone explain this to me?


Solution

  • The Task.Run method invokes the action delegate on the ThreadPool. So after calling twice this method, the ThreadPool spawned 2 threads to handle the request for work. At that point your program had at least 3 threads alive: the main thread of the process, and 2 background threads owned by the ThreadPool. Seeing the two background threads running concurrently is the expected behavior. Whether the Main method is async is irrelevant.

    In case you had invoked the Task.Run 100 times, the ThreadPool wouldn't have spawned 100 threads instantly to satisfy the demand. You can find a description of ThreadPool's algorithm that creates and destroys threads here or here. It is not very complicated. It involves a threshold that is equal to the number of CPU cores in your machine, and is adjustable with the SetMinThreads API.