I
I
Igor Garbuz2021-02-19 20:29:55
C++ / C#
Igor Garbuz, 2021-02-19 20:29:55

How do await chains work in C#?

Hello. Now I'm studying asynchrony in C # and decided to check the await call chains.
I read many times that if await encounters a Task that has not yet been executed, then the method returns an incomplete task and execution continues in the calling method.
Why, after the await Method2Async() line in the first example, control is not transferred to Main, but Method2Async begins to execute on the same thread?
And in the second example, when instead of await Method2Async() write await Task.Run(Method2Async) , the opposite is true. The result will be a different conclusion. I would be glad if someone tells more about how such chains work or threw off some source. Thanks in advance for your reply.
one:

static void Main(string[] args)
        {
            Console.WriteLine("MAIN BEFORE Method1Async THREAD ID : " + Thread.CurrentThread.ManagedThreadId);
            Task method1Task = Method1Async();
            Console.WriteLine("MAIN AFTER Method1Async THREAD ID : " + Thread.CurrentThread.ManagedThreadId);
            Console.Read();
        }

        static async Task Method1Async()
        {
            Console.WriteLine("Starting Method1Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            await Method2Async(); // Меняется только эта строка
            Console.WriteLine("End Method1Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }
        
        static async Task Method2Async()
        {
            Thread.Sleep(100);
            Console.WriteLine("Starting Method2Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            await Task.Run(() =>    
            {
                Thread.Sleep(100);
                Console.WriteLine("INNER TASK STARTED Thread id: " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(2000);
                Console.WriteLine("INNER TASK ENDED Thread id: " + Thread.CurrentThread.ManagedThreadId);
            });;
            Console.WriteLine("End Method2Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }

Output:
MAIN BEFORE Method1Async THREAD ID : 1
Starting Method1Async Thread id: 1
Starting Method2Async Thread id: 1
MAIN AFTER Method1Async THREAD ID : 1
INNER TASK STARTED Thread id: 5
INNER TASK ENDED Thread id: 5
End Method2Async Thread id: 5
End Method1Async Thread id: 5

2:
static void Main(string[] args)
        {
            Console.WriteLine("MAIN BEFORE Method1Async THREAD ID : " + Thread.CurrentThread.ManagedThreadId);
            Task method1Task = Method1Async();
            Console.WriteLine("MAIN AFTER Method1Async THREAD ID : " + Thread.CurrentThread.ManagedThreadId);
            Console.Read();
        }

        static async Task Method1Async()
        {
            Console.WriteLine("Starting Method1Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            await Task.Run(Method2Async); // Меняется только эта строка
            Console.WriteLine("End Method1Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }
        
        static async Task Method2Async()
        {
            Thread.Sleep(100);
            Console.WriteLine("Starting Method2Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            await Task.Run(() =>    
            {
                Thread.Sleep(100);
                Console.WriteLine("INNER TASK STARTED Thread id: " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(2000);
                Console.WriteLine("INNER TASK ENDED Thread id: " + Thread.CurrentThread.ManagedThreadId);
            });;
            Console.WriteLine("End Method2Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }

Output:
MAIN BEFORE Method1Async THREAD ID : 1
Starting Method1Async Thread id: 1
MAIN AFTER Method1Async THREAD ID : 1
Starting Method2Async Thread id: 4
INNER TASK STARTED Thread id: 7
INNER TASK ENDED Thread id: 7
End Method2Async Thread id: 7
End Method1Async Thread id: 7

Answer the question

In order to leave comments, you need to log in

2 answer(s)
V
Vasily Bannikov, 2021-02-19
@vabka

I have read many times that if await encounters a Task that has not yet been executed, then the method returns an incomplete task and execution continues in the calling method.

Apparently, you did not finish reading, or misunderstood.
I'll take your first example, but modify it a bit to make it clearer what's going on:
static async Task Method1Async()
        {
            Console.WriteLine("Starting Method1Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            var task = Method2Async();
            await task; // До этой точки код выполняется синхронно (если таска ещё не готова)
            Console.WriteLine("End Method1Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }
        
        static async Task Method2Async()
        {
            Thread.Sleep(100); // Thread.Sleep - это блокирующая операция
            Console.WriteLine("Starting Method2Async Thread id: "+ Thread.CurrentThread.ManagedThreadId);
            await Task.Yield(); // До этой точки код выполняетя синхронно. Task.Yield освобождает поток всегда
            Console.WriteLine("End Method2Async Thread id: " + Thread.CurrentThread.ManagedThreadId);
        }

You can find out more if you google TAP.docx

J
JhoOtvertka, 2021-02-25
@JhoOtvertka

My friend, this is exactly the place where they ask if they haven’t found it, haven’t understood it, haven’t read it

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question