Планирование задачи против продолжения
Предположим, у меня много задач с интенсивным использованием ЦП, которые выводятся в планировщике по умолчанию (все выполняются одновременно, например, через Task.Run
или же Task.Factory.StartNew
с планировщиком по умолчанию). Каждое задание имеет продолжение. Планировщик запустит несколько задач и переведет их в рабочее состояние. Когда эти задачи завершаются, планировщик имеет в своем списке смесь исходных задач и задач продолжения для планирования, и ему приходится выбирать между ними. Как это расставляет приоритеты для продолжения по сравнению с выполнением большего количества задач org. В частности, если задача выполнена и у нее есть продолжение, это продолжение получает приоритет в планировщике над другими задачами, которые уже поставлены в очередь.
Например, предположим, что шедулер запускает 2 (T1 и T2) из множества (T1...Tn) поставленных в очередь задач. Когда один из этих этапов будет завершен, планировщик обязательно запустит продолжение C1 или решит запустить T3? Это в любом случае детерминистическое, что он выберет? Возможно ли, что планировщик выберет выполнить больше задач и что для этого может быть значительная задержка между концом T1 и началом его продолжения C1?
Обновление: я запустил пример кода и добавил ответ - но я все еще хотел бы услышать, гарантировано ли это наблюдаемое поведение?
1 ответ
Итак, я попробовал это с образцом:
var continuations = new List<Task>();
for (int i = 0; i < 20; i++) {
int counter = i;
var continuation = Task.Run(() => {
Console.WriteLine($"T{counter}: Start.");
Thread.Sleep(500);
Console.WriteLine($"T{counter}: Complete.");
}).ContinueWith(t => {
Console.WriteLine($"C{counter}: Start.");
Thread.Sleep(50);
Console.WriteLine($"C{counter}: Complete.");
});
continuations.Add(continuation);
}
Task.WaitAll(continuations.ToArray());
Что выводит следующее - подразумевается, что планировщик устанавливает приоритет продолжения над запуском новых задач (но гарантировано ли это?):
T1: Start.
T2: Start.
T3: Start.
T0: Start.
T1: Complete.
T2: Complete.
T3: Complete.
C1: Start.
C3: Start.
T0: Complete.
C0: Start.
C2: Start.
C1: Complete.
T4: Start.
C3: Complete.
T5: Start.
C0: Complete.
T6: Start.
C2: Complete.
T7: Start.
T4: Complete.
C4: Start.
T5: Complete.
C5: Start.
T6: Complete.
C6: Start.
T7: Complete.
C7: Start.
C4: Complete.
C5: Complete.
T9: Start.
T8: Start.
C7: Complete.
T10: Start.
C6: Complete.
T11: Start.
T8: Complete.
C8: Start.
T9: Complete.
C9: Start.
T11: Complete.
C11: Start.
T10: Complete.
C10: Start.
C9: Complete.
C8: Complete.
T13: Start.
T12: Start.
C11: Complete.
T14: Start.
C10: Complete.
Вариация Здесь есть дополнительное выполнение в вызывающем потоке между запуском Задачи и регистрацией Продолжения.
var continuations = new List<Task>();
for (int i = 0; i < 20; i++) {
int counter = i;
var task = Task.Run(() => {
Console.WriteLine($"T{counter}: Start.");
Thread.Sleep(500);
Console.WriteLine($"T{counter}: Complete.");
});
Thread.Sleep(100); // Do some stuff before registering continuation.
var continuation = task.ContinueWith(t => {
Console.WriteLine($"C{counter}: Start.");
Thread.Sleep(150);
Console.WriteLine($"C{counter}: Complete.");
});
continuations.Add(continuation);
}
Task.WaitAll(continuations.ToArray());
Результат такой же, как и в предыдущем случае, т. Е. Продолжение задачи N имеет приоритет перед другими задачами в очереди.
T0: Start.
T1: Start.
T2: Start.
T3: Start.
T0: Complete.
C0: Start.
T1: Complete.
C1: Start.
C0: Complete.
T4: Start.
T2: Complete.
C2: Start.
C1: Complete.
T5: Start.
T3: Complete.
C3: Start.
C2: Complete.
T6: Start.
C3: Complete.
T7: Start.
T4: Complete.
C4: Start.
T5: Complete.
C5: Start.
C4: Complete.
T8: Start.
T6: Complete.
C6: Start.
C5: Complete.
T9: Start.
T7: Complete.
C7: Start.
C6: Complete.
T10: Start.
C7: Complete.
T11: Start.
T8: Complete.
C8: Start.
T9: Complete.
C9: Start.
C8: Complete.
T12: Start.
T10: Complete.
C10: Start.
C9: Complete.
T13: Start.
T11: Complete.
C11: Start.
T14: Start.
C10: Complete.
T15: Start.
C11: Complete.
T16: Start.
T12: Complete.
C12: Start.
T13: Complete.
C13: Start.
T14: Complete.
C14: Start.
C12: Complete.
T17: Start.
T15: Complete.
C15: Start.......
Еще один вариант. Здесь ВСЕ задачи ставятся в очередь в планировщике первыми (и могут быть запланированы для выполнения) ДО регистрации продолжений.
const int taskCount = 20;
var tasks = new List<Task>();
for (int i = 0; i < taskCount; i++) {
int counter = i;
var task = Task.Run(() => {
Console.WriteLine($"T{counter}: Start.");
Thread.Sleep(500);
Console.WriteLine($"T{counter}: Complete.");
});
tasks.Add(task);
}
Thread.Sleep(400);
var continuations = new List<Task>();
for (int i = 0; i < taskCount; i++) {
int counter = i;
var continuation = tasks[i].ContinueWith(t => {
Console.WriteLine($"C{counter}: Start.");
Thread.Sleep(150);
Console.WriteLine($"C{counter}: Complete.");
});
continuations.Add(continuation);
}
Task.WaitAll(continuations.ToArray());
Результат как и прежде - продолжения получают приоритет.
T0: Start.
T2: Start.
T3: Start.
T1: Start.
T1: Complete.
T3: Complete.
T0: Complete.
T2: Complete.
C2: Start.
C0: Start.
C3: Start.
C1: Start.
C2: Complete.
C1: Complete.
C0: Complete.
C3: Complete.
T7: Start.
T4: Start.
T6: Start.
T5: Start.
T6: Complete.
T5: Complete.
T7: Complete.
T4: Complete.
C6: Start.
C5: Start.
C7: Start.
C4: Start.
C7: Complete.
T8: Start.
C5: Complete.
T9: Start.
C6: Complete.
T10: Start.
C4: Complete.
T11: Start.
T8: Complete.
C8: Start.
T10: Complete.
C10: Start.
T9: Complete.
C9: Start.
T11: Complete.
C11: Start.
C8: Complete.
T12: Start.
C9: Complete.
T13: Start.
C10: Complete.
T14: Start.
C11: Complete.
T15: Start.
T14: Complete.
T12: Complete.
C12: Start.
T13: Complete.
C13: Start.
C14: Start.
T15: Complete.
C15: Start.
T16: Start.
C12: Complete.
C13: Complete.
T18: Start.
T17: Start.
C14: Complete.
T19: Start.
C15: Complete.
T16: Complete.
C16: Start.
T18: Complete.
C18: Start.
T17: Complete.
T19: Complete.
C19: Start.
C16: Complete.
C17: Start.
C18: Complete.
C19: Complete.
C17: Complete.