KinetonDev
@KinetonDev
Изучаю .NET!

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

Всем привет. Сейчас изучаю асинхронность в 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
  • Вопрос задан
  • 338 просмотров
Пригласить эксперта
Ответы на вопрос 2
@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
Ответ написан
@JhoOtvertka
Дружище, так это как раз то самое место, где спрашивают, если не нашел, не понял, не дочитал
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы