Вы писали:
Я хочу выполнить их один за другим с задержкой между ними.
Есть несколько проблем с вашим кодом.
В вашем примере вы создали перечислимое перечисление, не перечисляя его.Поэтому ваши задачи еще не начались.Они начнутся, как только вы начнете перечислять.Поэтому, как только вы используете foreach
, или ToList()
, или перечисление низкого уровня: GetEnumerator()
и MoveNext()
.
var tasks = new[] {1, 2, 3, 4, 5}.Select(async x => Console.WriteLine(x))
.ToList();
Каждая задача запускается сразу после ее создания, поэтому теперь они выполняются одновременно.
Ваша агрегатная функция также перечисляет каждый элемент по одному и выполняет ContinueWith
.Перечисление уже запускает задачу.
Посмотрите на исходный код Enumerable. Агрегат
public static TSource Aggregate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func)
{
... // some test for null
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext()) throw Error.NoElements(); // there must be at least one element
TSource result = e.Current;
while (e.MoveNext()) result = func(result, e.Current);
return result;
}
}
Итак, что он делает:
- Первый
MoveNext()
выполняет ваш оператор Select
один раз.Первая задача создана и должна быть запущена как можно скорее, возможно, немедленно. e.Current
используется для сохранения этой переменной текущей задачи Result
. - 2-й
MoveNext()
снова выполняет оператор Select
.Вторая задача создана и запланирована к запуску как можно скорее, возможно сразу. e.Current
содержит вашу вторую задачу.Используется для выполнения func(result, e.Current)
Эта функция будет делать следующее:
<first task>.ContinueWith(delay task).ContinueWith(<2ndTask>).
Результат помещается в переменный результат, MoveNext выполняется и т. д.
Помните: 1-я задача и 2-я задача уже запущены!
Так что на самом деле ваш агрегат выглядит примерно так:
Task result = empty task
for every task
{
Create it and schedule it to start running as soon as possible
Result = Result.ContinueWith(delayTask).ContinueWith(already running created task)
}
Теперь, что произойдет, если вы запуститезадача, и ContinueWith
еще одна уже запущенная задача?
Если вы посмотрите на Task.ContinueWith , он говорит, что параметр continuationAction
ContinueWith
означает:
Действие, запускаемое по завершении задачи.
Что произойдет, если вы запустите задачу, которая уже выполняется?Я не знаю, какой-нибудь тестовый код даст вам ответ.Мое лучшее предположение, что это ничего не сделаетЭто, конечно, не остановит уже запущенную задачу.
Это не то, что вы хотите!
Мне кажется, что это не то, что вы хотите.Вы хотите указать некоторые действия для последовательного выполнения с задержкой между ними.Примерно так:
Create a Task for Action 1, and wait until finished
Delay
Create a Task for Action 2, and wait until finished
Delay
...
Итак, вам нужна функция с вводом последовательности Actions и DelayTime.Каждое действие будет запущено с ожиданием до его завершения, после чего ожидается DelayTime.
Вы можете сделать это с помощью Aggregate, где на входе есть последовательность действий.Результат будет ужасным.Трудно читать, тестировать и поддерживать.Процедура будет намного проще для понимания.
Чтобы сделать ее совместимой с LINQ, я создам метод расширения.См. демистифицированные методы расширения
static Task PerformWithDelay(this IEnumerable<Action> actionsToPerform, TimeSpan delayTime)
{
var actionEnumerator = actionsToPerform.GetEnumerator();
// do nothing if no actions to perform
if (!actionEnumerator.MoveNext())
return Task.CompletedTask;
else
{ // Current points to the first action
Task result = actionEnumerator.Current;
// enumerate over all other actions:
while (actionEnumerator.MoveNext())
{
// there is a next action. ContinueWith delay and next task
result.ContinueWith(Task.Delay(delayTime)
.ContinueWith(Task.Run( () => actionEnumerator.Current);
}
}
}
Ну, если вы действительно, действительно хотите использовать агрегат, чтобы произвести впечатление на ваших коллег:
Task resultTask = actionsToPerform.Aggregate<Action, Task> (
action =>
{
previousResultTask.ContinueWith(Task.Delay(delayTime))
.ContinueWith(Task.Run( () => action));
});
Итак, мы собираем агрегациюс первым элементом вашей последовательности для каждого действия в вашей последовательности действий: ContinueWith нового Task.Delay и ContinueWith нового Task, который выполняет действие.
Проблема: исключение, если ваш ввод пуст.