TaskCreationOptions.AttachedToParent不会等待子任务完成。

7
根据MSDN的说法:
你可以使用AttachedToParent选项来表达结构化任务并行性,因为父任务隐式地等待所有子任务完成。
所以我有这段代码:
public async Task<int> GetIntAsync()
{
    var childTask = Task.Factory.StartNew(async () =>
    {
        await Task.Delay(1000);
    },TaskCreationOptions.AttachedToParent);

    return 1;
}

public async Task<ActionResult> Index()
{
    var watch = Stopwatch.StartNew();
    var task = GetIntAsync();
    var result = await task;
    var time = watch.ElapsedMilliseconds;

    return View();  
}

我想知道为什么时间是0而不是1000。

2
你不应该在async方法中使用StartNew(),因为它对它们的支持不好。相反,你应该使用Task.Run()(虽然它不支持AttachedToParent)。 - svick
3个回答

7
使用基于任务的异步模式(TAP)的代码通常不会使用AttachedToParentAttachedToParent是任务并行库(TPL)设计的一部分。TPL和TAP共享相同的Task类型,但在TAP代码中应避免使用许多TPL成员。
在TAP中,您可以通过使“父”异步方法await来自“子”异步方法返回的任务来支持“父”和“子”异步方法的概念:
public async Task<int> GetIntAsync()
{
  var childTask = Task.Run(async () =>
  {
    ...
    await Task.Delay(1000);
    ...
  });
  ...

  await childTask;
  return 1;
}

如果您有数量不定的子任务怎么办?比如监听TCP客户端并为每个客户端创建一个新任务。在集合中存储所有这些任务是没有意义的。否则,您需要创建另一个任务来删除已完成的任务以避免集合爆炸;这是非常丑陋的解决方案。 - drowa
2
@drowa:语句“任务通常(如果不是总是)由后台线程执行”是不正确的。任务代表完成的未来事件。虽然它们可能代表在线程池上执行的工作(Task.Run),但绝大多数任务代表I/O绑定的工作,不会在任何地方“运行”。要实现协作式关闭,请使用CancellationToken - Stephen Cleary
1
@Stephan Cleary:我会使用列表。但我的观点是,如果我能使用AttachedToParent,那么它会更加清晰。我认为这样做也更有效率,因为似乎此功能由简单计数器在内部实现。另外,对我来说似乎很自然的一件事就是能够创建“前台”任务,在其卸载期间不会被AppDomain中断。无论如何,谢谢您的意见。 - drowa
@Stephan Cleary:我添加了一个解决方案,用于具有动态数量“子项”的“父项”,它使用单个任务而不是任务列表。该解决方案很简单但不明显,这就是为什么我认为值得分享的原因。它避免了我们在使用具有无限数量短暂子项的任务列表时可能遇到的问题,这并不是一个罕见的情况。顺便说一句,该解决方案不需要父/子关系。每当您拥有无限数量的短暂任务时,都可以使用它。 - drowa
1
@RamilAliyev007:很简单,我只是忘记打字了!:D - Stephen Cleary
显示剩余7条评论

5

AttachedToParent仅附加到已调度的任务。您的异步方法返回的任务未被调度,而是(隐式地)来自TaskCompletionSource。


2

这是一个适用于动态子任务数量的解决方案。

通常情况下,使用列表会比较简单粗暴。

public async Task<int> GetIntAsync()
{
    var childTasks = new List<Task>();

    while (...)
    {
        ...
        childTasks.Add(Task.Run(...));
        ...
    }

    await Task.WhenAll(childTasks);

    return 1;
}

例如,如果子任务的生命周期很短且新任务被快速且无限制地创建,则列表将溢出。
相反,我们可以只使用一个任务。
public async Task<int> GetIntAsync()
{
    var childrenTask = Task.WhenAll();

    while (...)
    {
        ...
        childrenTask = Task.WhenAll(Task.Run(...), childrenTask);
        ...
    }

    await childrenTask;

    return 1;
}

注意,这是一个任务链表,随着任务完成,它会缩小。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接