Объясните задержку при вызове Task

При выполнении следующего кода наблюдаю картину, что несколько (3-4) выводов в консоль отрабатывают быстро, а затем следующие выводятся очень медленно. Ожидаю, что потоки будут запускаться быстро, выводить данные в консоль, ждать 60 с и завершаться. Почему не так? Использование TaskCreationOptions.LongRunning ситуацию несколько улучшает, но хочется понять в какие ограничения я упираюсь.

            for (int step = 0; step < 10000000; step++)
        {
            int index = step;
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("index=" + index);
                Console.WriteLine("thread=" + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(60000);                    
            });
        }
        Console.ReadLine();

Ответы (2 шт):

Автор решения: CrazyElf

Всё правильно, вы сделали несколько потоков и усыпили их. Они спят, но управление не отдают. Пока какой-то поток не закончится, новые не стартуют - у среды исполнения просто нет для этого свободных потоков. Если хотите, чтобы спящий поток не блокировал исполнение других потоков, используйте асинхронную версию задержки - await Task.Delay(60000);, она будет отдавать управление.

Пример можно посмотреть в документации, однако вам нужно не ждать как там исполнения каждой задачи через t.Wait(), а, например, собрать все созданные таким образом задачи в один массив и потом сделать Task.WaitAll(), чтобы подождать окончания сразу всех созданных задач одновременно.

→ Ссылка
Автор решения: tym32167

Когда вы используете TaskCreationOptions.LongRunning, вы стартуете по сути новый поток.

Запуск потока - дорогая операция. Сам поток - затратный ресурс.

Более того, предположим, что процессор выделил на ваш процесс 10 секунд, за 10 секунд он должен обойти ваши потоки и каждому потоку выделить какое то время.

Предположим, что ваш процессор однопоточный, а переключение его между потоками занимает 1 секунду (переключение между потоками - это дорогая операция).

Имея 2 потока на процесс, за 10 секунд процессор успеет отработать 5 секунд на первом потоке, потом секунда на переклчение и потом еще 4 секунды на второй поток.

Когда у вас 4 потока, то процессор 3 секунды потратит на переключения, 3 потока получат по 2 скуеды работы, один отработает 1 секунду.

Когда у вас 10 потоков, то процессор потратит все время на переключение между потоками и каждый поток получит минимум времени или совсем не получит.

Ну а теперь представьте, какая работа будет выполнена, если у вас условно 10 процессоров и 10000000 потоков.

Так как вы создаете потоки один за другим и потоки стартуют не сразу, у вас поначалу все более-менее работает. Как только потоков становится огромное количество, то потоки начинют тупить, так как на каждый поток процессор тратит все меньше и меньше времени.

Когда вы не используете TaskCreationOptions.LongRunning, вы по сути не знаете, будет ли для вас создан новый поток или задача будет поставлена в пул потоков.

Если задача будет поставлена в пул потоков, который имеет огарничение на количество потоков, то вы можете оказаться в ситуации, что вы своим thread.sleep заблокировали все потоки в пуле, и вам придется ждать, пока какой-то из потоков не освободится, чтобы выполнить очередную задачу. Потому в это случае вы можете увидеть, что первые несколько сообщений прошли быстро, а после надо ждать ваш таймаут, чтобы обработать очередное сообщение.


А вообще, вы поизучайте вопрос. Я уверен, что вы найдете рекомендацию, что Task.Factory.StartNew лучше использовать только когда вы знаете, что делаете. Когда не знаете - то начните с Task.Run.

→ Ссылка