0

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Armaho
  • 37
  • 5
  • 7
    You should do some research into what `Task.Run` actually does. It is literally the way to run tasks asynchronously from a synchronous method. You can't call asynchronous methods using `await` if your method is not declared `async`. That is where you can only call methods synchronously. – jmcilhinney Aug 29 '23 at 10:20
  • 3
    This is expected behaviour. You start the first Task, and then execution continues with starting the second Task. You are not awaiting the Result of either, so execution continues regardless. Then as each task progresses, you get the successive messages – Jonathan Willcock Aug 29 '23 at 10:21
  • Task.Run will still run the code inside asynchronously [see](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=net-7.0#system-threading-tasks-task-run(system-action)). [This answer](https://stackoverflow.com/a/9343733/18278998) might help you resolve your issue in a better way – Roe Aug 29 '23 at 10:22
  • 3
    `async` doesn't make things run asynchronously (it is purely syntactic). It only allows you to use `await`. – Olivier Jacot-Descombes Aug 29 '23 at 10:24
  • 1
    As a side-note, there is a race condition in the consumer loop `while (!intCollection.IsCompleted)`. The correct way to consume a `BlockingCollection` is the [`GetConsumingEnumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1.getconsumingenumerable) method. It's not the only correct way, but it's the simplest one. – Theodor Zoulias Aug 29 '23 at 11:55

1 Answers1

0

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.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104