我想同时启动Task
对象的集合,并等到所有对象都完成。以下代码显示了我想要的行为。
public class Program
{
class TaskTest
{
private Task createPauseTask(int ms)
{
// works well
return Task.Run(async () =>
// subsitution: return new Task(async () =>
{
Console.WriteLine($"Start {ms} ms pause");
await Task.Delay(ms);
Console.WriteLine($"{ms} ms are elapsed");
});
}
public async Task Start()
{
var taskList= new List<Task>(new[]
{
createPauseTask(1000),
createPauseTask(2000)
});
// taskList.ForEach(x => x.Start());
await Task.WhenAll(taskList);
Console.WriteLine("------------");
}
}
public static void Main()
{
var t = new TaskTest();
Task.Run(() => t.Start());
Console.ReadKey();
}
}
输出为:
Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------
现在我想知道是否可以准备我的任务并单独开始它们。为此,我将方法createPauseTask(int ms)
的第一行从return Task.Run(async () =>
更改为return new Task(async () =>
在Start()
方法中,我在WhenAll
之前包含一个taskList.ForEach(x => x.Start());
.但随后可怕的事情发生了。WhenAll
调用立即完成,输出为:
Start 2000 ms pause
Start 1000 ms pause
------------
1000 ms are elapsed
2000 ms are elapsed
有人可以告诉我我的错误是什么以及如何修复它吗?
问题是您使用的Task
构造函数接受Action
委托。 当你说
var task = new Task(async () => { /* Do things */ });
你不是在创建一个"做事"的任务;相反,你被创建了一个执行返回任务的动作的任务,而这个(内部(任务就是"做事"的任务。 创建这个(内部(任务非常快,因为委托在第一个await
返回Task
,并且几乎立即完成。 由于委托是Action
,生成的Task
实际上被丢弃,现在可以不再用于等待。
当您在外部任务上调用await Task.WhenAll(tasks)
时,您只是在等待创建内部任务(几乎是立即的(。 之后,内部任务将继续运行。
有一些构造函数覆盖允许你做你想做的事,但你的语法会更麻烦一些,就像 Paul 的答案一样:
public static Task<Task> CreatePauseTask(int ms)
{
return new Task<Task>(async () =>
{
Console.WriteLine($"Start {ms} ms pause");
await Task.Delay(ms);
Console.WriteLine($"{ms} ms are elapsed");
});
}
您现在有一个执行相同操作的任务,但这次返回内部Task
。 要等待内部任务,您可以执行以下操作:
await Task.WhenAll(await Task.WhenAll(taskList));
内部等待返回内部任务的列表,外部等待等待它们。
如前所述,使用构造函数创建未启动的任务是一个真正应该进入的领域,只有当你有一个高度特定的要求,其中使用Task.Run()
或简单地调用任务返回方法的更标准的做法不符合要求(它将在 99.99% 的情况下完成(。
大多数Task
构造函数都是在 async-await 存在之前创建的,并且可能只是出于遗留原因仍然存在。
编辑:可能还会有所帮助的是查看 2 个委托的签名,C# lambda 表示法允许我们 - 通常方便地 - 忽略这些签名。
对于new Task(async () => { /* Do things */ })
我们有
async void Action() { }
(void
是这里的主要问题(。
对于new Task<Task>(async () => { /* Do things */ })
我们有
async Task Function() { }
这两个 lambda 在语法上相同,但在语义上不同。
我不知道你为什么这么喜欢Task.Run
.只是不需要在代码中多次使用它。
我也想知道你在哪里读到使用Task
构造函数是推荐的做法。
无论如何,不使用Task
构造函数,将是这样的:
class TaskTest
{
private async Task CreatePauseTask(int ms)
{
Console.WriteLine($"Start {ms} ms pause");
await Task.Delay(ms);
Console.WriteLine($"{ms} ms are elapsed");
}
public async Task Start()
{
var taskList = new List<Task>(new[]
{
CreatePauseTask(1000),
CreatePauseTask(2000)
});
await Task.WhenAll(taskList);
Console.WriteLine("------------");
}
}
static void Main()
{
var t = new TaskTest();
t.Start();
Console.ReadKey();
}
这输出:
Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------
使用Task
构造函数将是:
class TaskTest
{
private Task CreatePauseTask(int ms)
{
return new Task<Task>(async () =>
{
Console.WriteLine($"Start {ms} ms pause");
await Task.Delay(ms);
Console.WriteLine($"{ms} ms are elapsed");
}, TaskCreationOptions.DenyChildAttach);
}
public async Task Start()
{
var taskList = new List<Task>(new[]
{
CreatePauseTask(1000),
CreatePauseTask(2000)
});
taskList.ForEach(t => t.Start(TaskScheduler.Default));
await Task.WhenAll(taskList);
Console.WriteLine("------------");
}
}
static void Main()
{
var t = new TaskTest();
t.Start();
Console.ReadKey();
}
这输出:
Start 1000 ms pause
Start 2000 ms pause
------------
1000 ms are elapsed
2000 ms are elapsed
这里的问题是Task.Run
理解Task
返回函数,而Task
构造函数则不理解。因此,使用Task
构造函数将调用将在第一个阻塞等待 (await Task.Delay(ms);
( 时返回的函数。
这是预期行为。