Как работают цепочки await в C#?



@KinetonDev

Всем привет. Сейчас изучаю асинхронность в C# и решил проверить цепочки вызовов await.
Я много раз читал, что если await встречает Таск, который ещё не выполнен, то метод возвращает незавершённый таск и выполнение продолжается в вызывающем методе.
Почему после строки await Method2Async() в первом примере управление не передаётся в Main, а начинает выполняться Method2Async в том же потоке?
А во втором примере, когда вместо await Method2Async() написать await Task.Run(Method2Async), наоборот. В итоге будет разный вывод. Был бы рад, если кто-нибудь расскажет больше о том, как работают такие цепочки или скинул какой-нибудь источник. Заранее спасибо за ответ.
1:

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);
        }

Вывод:
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);
        }

Вывод:
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


Решения вопроса 0


Ответы на вопрос 1



@vabka Куратор тега C#

Я много раз читал, что если await встречает Таск, который ещё не выполнен, то метод возвращает незавершённый таск и выполнение продолжается в вызывающем методе.

Видимо, вы не дочитали, либо недопоняли.

Возьму ваш первый пример, но немного его видоизмению, чтобы было понятнее, что проиходит:

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);
        }

Подробнее можете узнать, если загуглите TAP.docx

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *