Pull to refresh

Could async/await magic create thread, or it is always: “There is no thread”?

Level of difficultyMedium
Reading time4 min
Views544

When we are told “There is no thread” we can easily come to an opinion that it is impossible at ALL that asynchronous operation could create thread, but it would be wrong opinion. Simple code example proves the opposite.

Those who are easy to treat the sentence as the universal rule are easy to understand. They would like to simplify the subject and to cut amount of theory they should study and remember. Besides to many it is new level of knowledge to discover there is other layer of classes to manage async-operations behavior beside the Tasks and and SynchronizationContext is one among them.


So, there is example of code, that sure creates additional thread by asynchronous operation in a console (it is important it is console application, further we will see why!) application:

        static async Task Main()
        {
            SomeMethodAsync1();
            Console.WriteLine($"ThrID={Thread.CurrentThread.ManagedThreadId}");
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"MAIN Iter={i}");
                Thread.Sleep(100);
            }
        }

        static async Task SomeMethodAsync1()
        {
            await Task.Yield();
            Console.WriteLine($"TrID={Thread.CurrentThread.ManagedThreadId}");
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"MethodIter={i}");
                Thread.Sleep(100);
            }
        }

This code example produces the following results:

ThrID=1

TrID=5

MAIN Iter=0

MethodIter=0

MAIN Iter=1

MethodIter=1

MAIN Iter=2

MethodIter=2

MethodIter=3

MAIN Iter=3

MAIN Iter=4

MethodIter=4

And you can see that the two functions main() and SomeMethodAsync1() finely are executed in different threads with ThrID=1 and TrID=5

 And we can find it in debug mode in Threads-window as following:

Not Flagged                12268  1          Main Thread   Main Thread            System.Private.CoreLib.dll!System.Threading.Thread.Sleep
Not Flagged    >           26956  5          Worker Thread           .NET ThreadPool Worker            PrimMath.dll!PrimMath.Program.SomeMethodAsync1

We can see that the program takes threads from ThreadPool, so it doesn’t create threads it borrows them from ThreadPool. But beside this it is obvious that the call to asynchronous function SomeMethodAsync1() add new thread in our program, so we have one thread before the call and two threads after. So, the statement “There is no thread” is false at least for the case.

If you don’t want to study sample code from a unknown stranger you can look at Stephen Toub’s post: How Async/Await Really Works in C# , part: “SynchronizationContext and ConfigureAwait”

There is example of console code, which is mentioned there as “our timing sample” and which creates additional thread for “async lambda-function”. Actually, the fact that the asynchronous method creates an additional thread is the problem that the author of the Post further successfully solves when he replaces the SynchronizationContext for this original example.

From that example by Stephen Toub, we should have concluded that the presence or absence of a thread inside an asynchronous method is controlled precisely by SynchronizationContext, which, in a sense, can be considered an additional scheduler, which is provided to us by the execution environment .NET, and which you can redefine for your tasks at any time.

And now armed with this knowledge that the presence or absence of a thread for asynchronous operations is controlled by the SynchronizationContext, let's take a look at the example that is analyzed in the article “There is no thread”:

private async void Button_Click(object sender, RoutedEventArgs e)
{
  byte[] data = ...
  await myDevice.WriteAsync(data, 0, data.Length);
}

I want to draw your attention to the name of the Button_Click() function, which unambiguously tells us which SynchronizationContext is used in this example. It is the SynchronizationContext of the current window (or the entire UI). UI SynchronizationContext does not really create threads, it packages asynchronous calls into messages for a single UI thread, queues them for execution in this single thread.

 That's why at the very beginning I drew your attention to the fact that the code of my example (as well as the code of the example from Stephen Toub from the paragraph about SynchronizationContext) is executed in a console application. The windowed application and the console application use different SynchronizationContext, which means they control the threading model, which is used in asynchronous methods, differently.

You see even in this example the additional thread that is used by async-function is not created, it is borrowed from Thread Pool but it is possible to create our own SynchronizationContext class version that would literary create new thread when async operation requests for executing it’s continuation.

As a conclusion from this you can take that if you are going to use asynchronous operations in you code you have to be prepared to understand and possibly to manage a threading model that your environment or framework define as a base for asynchrony.

Tags:
Hubs:
+1
Comments0

Articles