如何启动异步任务对象



我想同时启动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);( 时返回的函数。

这是预期行为。

最新更新