Объясните задержку при вызове 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 шт):
Всё правильно, вы сделали несколько потоков и усыпили их. Они спят, но управление не отдают. Пока какой-то поток не закончится, новые не стартуют - у среды исполнения просто нет для этого свободных потоков. Если хотите, чтобы спящий поток не блокировал исполнение других потоков, используйте асинхронную версию задержки - await Task.Delay(60000);, она будет отдавать управление.
Пример можно посмотреть в документации, однако вам нужно не ждать как там исполнения каждой задачи через t.Wait(), а, например, собрать все созданные таким образом задачи в один массив и потом сделать Task.WaitAll(), чтобы подождать окончания сразу всех созданных задач одновременно.
Когда вы используете 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.