Почему у библиотеки параллельных задач есть «скрытый» тайм-аут на 1 секунду для планирования задач при определенных условиях? - PullRequest
4 голосов
/ 29 февраля 2012

Мой ноутбук имеет 2 логических процессора, и я наткнулся на сценарий, в котором, если я планирую 2 задачи, которые занимают более 1 секунды, но не назначает их длительными, последующие задачи запускаются через 1 секунду.Можно ли изменить этот тайм-аут?

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

Этот метод консольного приложения должен демонстрировать поведение для машины с любым числом процессоров:

static void Main(string[] args)
{
    var timer = new Stopwatch();
    timer.Start();

    int numberOfTasks = Environment.ProcessorCount;

    var rudeTasks = new List<Task>();
    var shortTasks = new List<Task>();

    for (int index = 0; index < numberOfTasks; index++)
    {
        int capturedIndex = index;
        rudeTasks.Add(Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Starting rude task {0} at {1}ms", capturedIndex, timer.ElapsedMilliseconds);
            Thread.Sleep(5000);
        }));
    }

    for (int index = 0; index < numberOfTasks; index++)
    {
        int capturedIndex = index;
        shortTasks.Add(Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Short-running task {0} running at {1}ms", capturedIndex, timer.ElapsedMilliseconds);
        }));
    }

    Task.WaitAll(shortTasks.ToArray());
    Console.WriteLine("Finished waiting for short tasks at {0}ms", timer.ElapsedMilliseconds);

    Task.WaitAll(rudeTasks.ToArray());
    Console.WriteLine("Finished waiting for rude tasks at {0}ms", timer.ElapsedMilliseconds);

    Console.ReadLine();
}

Вот выходные данные приложенияна моем ноутбуке 2 proc:

Starting rude task 0 at 2ms
Starting rude task 1 at 2ms
Short-running task 0 running at 1002ms
Short-running task 1 running at 1002ms
Finished waiting for short tasks at 1002ms
Finished waiting for rude tasks at 5004ms

Press any key to continue . . .

Строки:

Short-running task 0 running at 1002ms
Short-running task 1 running at 1002ms

указывают, что есть тайм-аут на 1 секунду или что-то в этом роде, позволяющее выполнять более короткие задачи по расписанию«грубые» задачи.Вот о чем я спрашиваю.

Ответы [ 3 ]

4 голосов
/ 29 февраля 2012

Это стандартное поведение для планировщика пула потоков.Он пытается сохранить количество активных потоков равным количеству ядер.Но вы не можете делать эту работу очень хорошо, когда ваши задачи выполняют много блокировок вместо выполнения.Спит в твоем случае.Дважды в секунду это позволяет другому потоку работать, чтобы попытаться уменьшить отставание.Похоже, у вас двухъядерный процессор.

Правильный обходной путь - это использовать TaskCreationOptions.LongRunning, чтобы планировщик использовал обычный поток вместо потока пула потоков.Неправильный обходной путь - использовать ThreadPool.SetMinThreads.Но, возможно, вам следует сосредоточиться на выполнении реальной работы в ваших задачах, Sleep () не очень хорошая симуляция этого.

4 голосов
/ 29 февраля 2012

Поведение, которое вы видите, не относится к TPL, оно относится к планировщику по умолчанию TPL . Планировщик пытается увеличить количество потоков, чтобы те два, которые работают, не «нагружали» ЦП и подавляли другие. Также полезно избегать тупиковых ситуаций, если два запущенных запускаются и ждут самих Задач.

Если вы хотите изменить поведение планирования, вам может потребоваться реализация вашего собственного TaskScheduler .

1 голос
/ 29 февраля 2012

Проблема в том, что планировщику требуется некоторое время для запуска новых задач, поскольку он пытается определить, является ли задача длительной. Вы можете сказать TPL, что задача долго выполняется как параметр задачи:

for (int index = 0; index < numberOfTasks; index++)
{
    int capturedIndex = index;
    rudeTasks.Add(Task.Factory.StartNew(() =>
    {
        Console.WriteLine("Starting rude task {0} at {1}ms", capturedIndex, timer.ElapsedMilliseconds);
        Thread.Sleep(3000);
    }, TaskCreationOptions.LongRunning));
}

В результате:

Starting rude task 0 at 11ms
Starting rude task 1 at 13ms
Starting rude task 2 at 15ms
Starting rude task 3 at 19ms
Short-running task 0 running at 45ms
Short-running task 1 running at 45ms
Short-running task 2 running at 45ms
Short-running task 3 running at 45ms
Finished waiting for short tasks at 46ms
Finished waiting for rude tasks at 3019ms
...