【问题标题】:What's the purpose of the TPL ContinueWith method?TPL ContinueWith 方法的目的是什么?
【发布时间】:2017-01-20 08:17:39
【问题描述】:

我对 TPL ContinueWith 方法感到困惑。我不明白为什么需要它。 Here's an example from MSDN 说明如何使用ContinueWith

static void SimpleContinuationWithState()
{
   int[] nums = { 19, 17, 21, 4, 13, 8, 12, 7, 3, 5 };
   var f0 = new Task<double>(() => nums.Average());
   var f1 = f0.ContinueWith(t => GetStandardDeviation(nums, t.Result));

   f0.Start();
   Console.WriteLine("the standard deviation is {0}", f1.Result);
}

看来我可以删除ContinueWith 调用而不改变结果:

static void SimpleContinuationWithState()
{
   int[] nums = { 19, 17, 21, 4, 13, 8, 12, 7, 3, 5 };
   var f0 = new Task<double>(() => GetStandardDeviation(nums, nums.Average()));

   f0.Start();
   Console.WriteLine("the standard deviation is {0}", f0.Result);
}

这个标准差示例一定是人为的示例,但我想不出使用 ContinueWith 的理由。 (除非某些库调用而不是我创建了任务)在每种情况下,我不能将 ContinueWith 调用拉到原始任务中吗?它仍然会异步运行。一定有什么我不明白的地方。

【问题讨论】:

  • 查看其他重载,特别是那些接受TaskContinuationOptions的重载
  • 如果你调用一个已经返回Task&lt;T&gt;的方法,你会怎么做?您有一个人为的示例,其中原始部分实际上根本不是异步的,这一事实在这里没有帮助......将其想象为“我想调用一个 Web 服务,然后当它返回时我想调用第二个带有第一个结果的 Web 服务。”现在想想如何在不阻塞线程的情况下做到这一点。
  • @JonSkeet,如果可以的话,我会将您的评论标记为答案。我没有考虑过这种情况。

标签: c# task-parallel-library continuations


【解决方案1】:

您假设每个Task 只是一个在线程池线程中运行的同步方法。事实并非如此,您不应该那样想任务。 Task 是某种将在某个时候完成的工作,可能会因此产生一个值。它可能正在线程池线程中运行一个方法,它可能正在等待事件触发,它可能正在等待对网络请求的响应,或者等待您的硬盘驱动器完成将一些数据写入文件。

是的,如果您的 Task 是在线程池线程中运行的特定同步代码,并且您总是希望在同一线程池线程上立即运行更多同步代码,并且您是控制创建该Task 的人,并且没有其他需要代表该中间​​结果的Task,那么您可以更改用于生成Task 的方法,如您所展示的(如果您在那种情况下,你最好这样做)。不过,这最终是一个相当狭窄的案例。

【讨论】:

    【解决方案2】:

    正如我在博客中描述的那样,there is exactly one use case for ContinueWith: dynamic task parallelism

    它不应该用于:'

    • 异步代码。
    • 数据并行性。
    • 静态任务并行性。

    “动态任务并行”是指有一堆 CPU 密集型工作要使用多个线程来完成(“并行”),并且您将工作分成多个 CPU 密集型任务(“任务并行” ),并且你不知道你需要多少任务,直到你已经在处理它(“动态任务并行”)。

    换句话说,您几乎不应该使用ContinueWith

    【讨论】:

      【解决方案3】:

      调用.Result 是一个阻塞操作,对于同步代码 - 特别是当您知道结果已经存在时。使用ContinueWith结果可用时安排回调 - async 代码(尽管不是通过async/await 模式)。请注意,如果结果已经可用,则继续将直接调用回调用线程。

      您现有的代码很糟糕,可能会导致致命的死锁。不要这样做 :) 在这种情况下,你正在侥幸逃脱,但是在任何“同步异步”(又名“调用 .Wait() 或访问 @987654327 @)。

      【讨论】:

      • 我对我的代码将如何死锁感到困惑。如果我的继续以任何方式在主线程上等待,我可以看到我会如何陷入死锁。但在我的示例中,唯一的阻塞操作是 Result 属性。
      • @user2023861 简单;如果async 操作尝试执行await,默认情况下它会尝试通过同步上下文调用;最简单的例子是 winforms 之类的东西,但它同样适用于任何东西(asp.net、wcf 等);现在 - 您的 UI 线程已开始 async 操作,并且一些 cogs 在后台旋转(网络等);现在,您示例中的 UI 代码不使用 await,而是调用 .Result.Wait() - 这意味着 UI 线程被阻塞等待结果。但是工作人员本身试图向UI线程发送一些东西来完成......
      • @user2023861 操作,但在 UI 线程可用之前无法执行。在异步操作完成之前,UI 线程无法变为可用。一句话:死锁。这不是想象的情况。这里有很多关于 SO 的问题,询问他们的代码为什么被阻止。我自己问过一个! stackoverflow.com/questions/13621647/…
      • 我明白了。我以为你在谈论我从 MSDN 复制的原始帖子中的代码中的潜在死锁。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      • 2020-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-22
      相关资源
      最近更新 更多