async/await и создание потоков

173
02 февраля 2018, 18:54

Прочитал несколько источников про async/await, где писалось, что якобы никаких дополнительных потоков эти конструкции не создают.

Решил написать тестовый код:

 public static void Main()
        {
            var t = Test();
             t.Wait();
        }
        static async Task Test()
        {
            var t = Test2();
            for (; ; )
            {
                await Task.Delay(1000);
                Console.WriteLine("0");
            }
        }
        static async Task Test2()
        {
            for (; ; )
            {
                await Task.Delay(1000);
                Console.WriteLine("1");
            }
        }

Так вот, Visual Studio показывает, что было создано 2 потока.

  1. Главный поток, который ушел обрабатывать один из асинхронных методов
  2. Другой был создан для обработки второй асихронной задачи

А иногда, если верить дебагеру, их 3.

Как так?

Или все таки потоки не создаются при использовании только IO операций, а при каких-то вычислительных потоках CLR определяет необходимость создания потока?

Или под "не создает потоков" понималось, что берутся готовые потоки из пула, но множество потоков в программе в единицу времени имеет место быть?

Или имеется в виду, что старается использовать, как можно меньше потоков? Так если крутится 2 асинхронных операции и они не пресекаются по времени выполнения, то используется 1 поток, а если они параллельно крутятся, то CLR выгодно 2 потока крутить?

Answer 1

Главный поток у вас висит на операции t.Wait(); и ничего не выполняет.

Вы не установили контекст синхронизации - а потому все продолжения await выполняются в пуле потоков. Отсюда и второй поток - для того чтобы выполнять вывод в консоль. А если обе задачи просыпаются одновременно - то нужен и третий поток.

Тем не менее, как вы можете заметить, Task.Delay(1000) сам по себе ни в каком потоке не выполняется - потоки нужны только для вывода в консоль. Если вы запустите десять тысяч подобных задач - им для выполнения будет достаточно относительно небольшого числа потоков. В этом и выгода.

Кстати, способ избавиться от дополнительных потоков - есть, но для этого надо избавиться от вызова .Wait() и поставить какой-нибудь контекст синхронизации.

Например, можно взять QueueSynchronizationContext из моего ответа на вопрос "Зависает оператор await в оконном приложении / программа висит при вызове Task.Result или Wait". Вот такой код будет выполняться строго в одном потоке:

 public static void Main()
 {
     var syncCtx = new QueueSynchronizationContext(); 
     // вызывает внутри SynchronizationContext.SetSynchronizationContext(syncCtx);
     var t = Test(); // Важно: вызывать строго после SetSynchronizationContext
     syncCtx.WaitFor(t);
 }
READ ALSO
шина данных и pub/sub

шина данных и pub/sub

здравствуйте, пытаюсь понять что такое шина данных/шина сообщений(message bus) в распределенных системах

164
Выполнение js после его подгрузки в html

Выполнение js после его подгрузки в html

ЗдравствуйтеУ меня скрипт через ajax подгружает html контент

221
Обрабатывать событие keydown только в пределах нужного экрана

Обрабатывать событие keydown только в пределах нужного экрана

Есть 2 блока, где нужно событие keydown они на разных экранах страницы, keydown общийКак его ограничить, чтобы если видна одна область, то работает...

207
Параметры функции VueJS

Параметры функции VueJS

Подскажите, есть такая функция которая вызывается в VueJS следующим образом событие myEvent возвращает результат (result) при вызове:

219