Прочитал несколько источников про 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 потока.
А иногда, если верить дебагеру, их 3.
Как так?
Или все таки потоки не создаются при использовании только IO операций, а при каких-то вычислительных потоках CLR определяет необходимость создания потока?
Или под "не создает потоков" понималось, что берутся готовые потоки из пула, но множество потоков в программе в единицу времени имеет место быть?
Или имеется в виду, что старается использовать, как можно меньше потоков? Так если крутится 2 асинхронных операции и они не пресекаются по времени выполнения, то используется 1 поток, а если они параллельно крутятся, то CLR выгодно 2 потока крутить?
Главный поток у вас висит на операции 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);
}
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости