比方说,我有许多任务,我想一一延迟执行它们。我的想法是通过与Aggregate
组合并在每对之间插入ContinueWith
将Task.Delay()
折叠成一个任务:
var tasks = new[] {1, 2, 3, 4, 5}.Select(async x => Console.WriteLine(x));
var superTask =
tasks.Aggregate(Task.CompletedTask, async (task1, task2) =>
await (await task1.ContinueWith(_ => Task.Delay(1000))).ContinueWith(_ => task2));
await superTask;
问题是为什么它不起作用?
答案 0 :(得分:0)
有几个问题。首先,您不必等待延迟,因此延迟甚至不存在。其次,以这种方式调用Tasks会立即自动启动-您需要改用构造函数方法(请参见this question)。
这部分代码将满足您的需求。
var tasks = new[] { 1, 2, 3, 4, 5 }
.Select
(
x => new Task( () => Console.WriteLine(x) )
);
var superTask =
tasks.Aggregate
(
Task.CompletedTask,
async (task1, task2) =>
{
await task1;
await Task.Delay(1000);
task2.Start();
}
);
await superTask;
所有这些,我高度怀疑这是正确的方法。只需编写一个简单的循环来遍历数组,然后一次处理它们。甚至不需要异步。
答案 1 :(得分:0)
您写道:
我想一个接一个地执行它们,并且之间要有延迟。
您的代码有几个问题。
在您的示例中,您创建了一个未枚举的枚举。因此,您的任务尚未开始。它们将在您开始枚举时立即启动。因此,一旦您使用foreach
或ToList()
或低级枚举:GetEnumerator()
和MoveNext()
。
var tasks = new[] {1, 2, 3, 4, 5}.Select(async x => Console.WriteLine(x))
.ToList();
每个任务一创建就开始运行,因此现在它们同时运行。
您的汇总函数还会枚举每个元素,并执行您的ContinueWith
。枚举已经开始执行任务。
看看source code of Enumerable.Aggregate
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
。MoveNext()
再次执行Select
语句。创建第二个任务,并计划将其尽快运行,并立即运行。e.Current
包含您的第二项任务。它用于执行func(result, e.Current)
此功能将执行以下操作:
<first task>.ContinueWith(delay task).ContinueWith(<2ndTask>).
将结果放入可变结果中,完成MoveNext,等等。
请记住:第一个任务和第二个任务已经在运行!
因此,实际上您的集合是这样的:
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
的{{1}}参数表示:
任务完成时要运行的动作。
如果运行已经运行的任务会怎样?我不知道,一些测试代码将为您提供答案。我最好的猜测是它什么也不会做。当然,它不会停止已经运行的任务。
在我看来,这不是您想要的。您要指定一些操作以在延迟时间之间顺序执行。像这样:
ContinueWith
所以,您想要的是一个函数,该函数输入了一系列的动作和一个DelayTime。每个动作都将开始,等待直到完成,然后等待DelayTime。
您可以使用汇总来执行此操作,其中输入是一系列操作。结果将是可怕的。难以阅读,测试和维护。一个过程将更容易理解。
要使其与LINQ兼容,我将创建一个扩展方法。参见extension methods demystified
Create a Task for Action 1, and wait until finished
Delay
Create a Task for Action 2, and wait until finished
Delay
...
好吧,如果您确实希望使用汇总表来打动您的同事:
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);
}
}
}
因此,对于操作序列中的每个操作,我们将序列的第一项作为聚合的种子:ContinueWith一个新的Task.Delay,ContinueWith一个新的执行该操作的Task。
问题:如果您输入的内容为空,则为异常。