【问题标题】:Stuck at Task.WaitAll(tasks.ToArray()) while using Task.Start to trigger the tasks在使用 Task.Start 触发任务时卡在 Task.WaitAll(tasks.ToArray())
【发布时间】:2020-08-01 01:06:06
【问题描述】:

我们有类似下面的东西

List<string> uncheckItems = new List<string>();
for (int i = 0; i < 100; i++)
{
    uncheckItems.Add($"item {i + 1}");
}

var tasks = uncheckItems.Select(item =>
    new Task(async () => await ProcessItem(item))
);

// Do some preparations

foreach (var task in tasks)
{
    task.Start();
}

Task.WaitAll(tasks.ToArray());
Console.WriteLine("=====================================================All finished");

这似乎是有道理的,但程序永远无法到达所有完成的行。 如果我调整工作流程以立即运行任务,例如删除 task.Start() 循环并更改为

var tasks = uncheckItems.Select(async item =>
    await ProcessItem(item)
);

然后就可以了。

但是,我想知道

  1. 为什么会卡住?
  2. 有什么方法可以保持工作流(创建任务而不直接触发它们并稍后启动它们)并且仍然能够利用WaitAll()

【问题讨论】:

  • 您能否提供更多背景信息,为什么要在开始之前创建任务?
  • @GuruStron 好吧,不一定要那样。我只是在重构一些代码,不小心遇到了这个问题。实际的代码非常混乱,所以我正在考虑重构的范围,很高兴知道这是一种解决方法,所以如果我的重构计划落后于计划,我仍然可以交付一些东西。
  • 你为什么使用new Task(...).Start()
  • Task 构造函数不知道Task 返回方法。

标签: c# async-await task-parallel-library


【解决方案1】:

原因是lazy enumeration evaluation,您开始的任务与等待Task.WaitAll 不同。这可以通过下一个来解决:

var tasks = uncheckItems.Select(item =>
    new Task(async () => await ProcessItem(item))
)
.ToArray();

虽然它不会实现您等待所有ProcessItem 完成的目标(据我所知)。您可以执行 new Task(() =&gt; ProcessItem(item).GetAwaiter().GetResult()) 之类的操作,但我认为更改您的方法会更好,例如让 ProcessItem 返回“冷”任务或使用您的第二个 sn-p 并将任务创建移动到他们需要的位置开始吧。

【讨论】:

  • 最好使用ToArray而不是ToList,这样OP就可以直接调用Task.WaitAll(tasks)而不是调用ToArray。
  • 该代码产生未开始的任务,Task.WaitAll 永远不会完成。而且,如果您启动它们,它们将在 ProcessItem 产生后立即结束。
  • @PauloMorgado 该代码产生未启动的任务是的,作为 OP 问题中的第一个,用于解释为什么/如何“修复”第一个 OP 的问题。 他们将尽快完成......是的,这就是为什么编写代码 sn-p 之后的所有内容(尽管措辞不是最好的,我同意)。
【解决方案2】:

您应该在Task 的世界专家旁边才能使用构造函数。 documentation 对此提出警告:

此构造函数只应在需要将任务的创建和启动分开的高级场景中使用。

实例化 Task 对象并启动任务的最常见方法是调用静态 Task.Run(Action) 或 TaskFactory.StartNew(Action) 方法,而不是调用此构造函数。

如果需要一个没有任何操作的任务只是为了让 API 的使用者有一些东西要等待,则应该使用 TaskCompletionSource。

Task 构造函数生成一个未启动的 Task,正如您所发现的,它只会在调用 Task.Start() 时启动。

Task 构造函数也接收Action(或Action&lt;T&gt;),因此忽略返回值。这意味着,在开始后,任务将在async () =&gt; await ProcessItem(item) 产生时立即结束。

你需要的是:

await Task.WhenAll(Enumerable.Range(0, 100).Select(i => ProcessItem($"item {i + 1}"));

或者,如果你真的必须阻止:

Task
    .WhenAll(Enumerable.Range(0, 100).Select(i => ProcessItem($"item {i + 1}"))
    .GetAwaiter().GetResult();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-19
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-23
    • 2015-11-16
    相关资源
    最近更新 更多