Async

When they show us with some example that an asynchronous operation does not create a thread, they try to convince us that an asynchronous operation NEVER creates a thread and, in principle, cannot create one, but this is not true! A simple example with working code proves the opposite. Let's look at this example.

The logic of those who succumb to such suggestion is quite clear to me; they want to simplify their life, reduce the amount of theory that needs to be dealt with.

It would be interesting to understand the logic of those who support such a suggestion, passing off a truncated theory as a full-fledged one, fully realizing that everything is not as simple as we would like.


So, an example of code that will create an additional thread for the asynchronous operation code in a console (this is important! why, we will see later) application looks like this:

        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 generates the following output:

Actually, already from this output it is clear that two functions are executed in different threads with identifiers: ID=1 and ID=5.

You can take a closer look at these threads N1 and N5 in debug mode:

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 see that our threads are taken from the Thread Pool, that is, formally speaking, our SomeMethodAsync1() function does not create a thread, it takes an existing one from the Thread Pool. But there is no denying the fact that an asynchronous operation still uses an additional thread, that is, the statement There is no thread turns out to be false for this code because we clearly see that The thread with ID5 is definitely present.

But maybe somewhere, somehow I’m also deceiving you? If you don’t believe me and my example and your own eyes, you can also turn to the unforgettable Stephen Toub and his already pretty boring (I hope this is not so 🙂 work How Async/Await Really Works in C#. Paragraph:

SynchronizationContext and ConfigureAwait.

There is an example of console code, which is referred to later in that Post as our timing sample and which also creates an additional thread for async lambda functions. Actually, the fact that the asynchronous method creates an additional thread is the problem that the author of the Post successfully solves when he replaces the SynchronizationContext for this original example. Actually, from that example by Stephen Tobe, you should have concluded that the presence or absence of a thread inside an asynchronous method is controlled precisely using the SynchronizationContext, which, in a sense, can be considered an additional scheduler, which is provided to us by the .NET runtime , and which you can redefine to suit your needs at any time.

But now, armed with this knowledge that the presence or absence of flow
for asynchronous operations, it is managed by the synchronization context (SynchronizationContext),
let's take a look at the example discussed in the article There is no thread. (There's no flow):

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 clearly tells us which SynchronizationContext is used in this example, this is the SynchronizationContext of the current window (or the entire UI). The UI SynchronizationContext doesn't really create threads, it wraps asynchronous calls into messages for a single UI thread, and queues them for execution on that single thread.

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

Well, I hope I answered the question from @Grave18

Can the magic of async/await itself create threads or is it all done explicitly by those who write asynchronous functions?

Thank you again for the constructive question.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *