【问题标题】:How to chain independent C# tasks?如何链接独立的 C# 任务?
【发布时间】:2016-12-03 01:38:07
【问题描述】:

假设我有两个独立的异步函数(我无法控制)来创建任务对象:

Task A();
Task B();

和其他一些非异步函数

void X();

如何构建一个单独的任务链,按顺序执行所有这些任务并允许附加进一步的延续(将在 X 之后执行)?

如果我这样做:

Task Sequential()
{
   return A()
     .ContinueWith(t => { B(); })
     .ContinueWith(t => { X(); });
}

这不起作用,因为 B 将启动一个新的任务链。如果 B 需要很长时间才能完成,则 X 将首先执行(以及 Sequential 的调用者可能对返回的 Task 进行 ContinueWith 的任何其他操作)。我需要 X 成为单个任务链中的最后一个元素,以 B 任务为先例。

如果我这样做:

Task Sequential()
{
   return A()
     .ContinueWith(t => { B().ContinueWith(t => { X(); }); });
}

这只能部分解决问题,因为即使 A、B 和 X 现在将按顺序执行,如果调用者执行 Sequential().ContinueWith,则该继续将与 B 和 X 并行执行,而不是在 X 之后执行。

【问题讨论】:

  • Alex 的回答是您想要的方式。但出于您的目的,您要完成的工作需要使用承诺(TaskCompletionSource<T> 服务)。
  • 如果你说AB是独立的,Task.WaitAll(A(), B()).ContinueWith(t => X())不是一个选项吗?
  • @VMAtm,你的意思是Task.WhenAll
  • 是的,WhenAll 不会阻塞线程。

标签: c# task-parallel-library


【解决方案1】:

有什么理由不使用 await 吗?例如,

async Task Sequential()
{
    await A();
    await B();
    X();
}

【讨论】:

    【解决方案2】:

    假设您不能像其他答案中所建议的那样使用async/await(如果可以,您应该),自从在.NET 4.0 中引入Task 以来,有一个漂亮的小扩展方法可以满足这种情况:@ 987654324@。它接收一个Task<Task>(或Task<Task<TResult>>)并将其“展平”为一个连续的Task(或分别为Task<TResult>),这表示外部任务和内部任务均已完成。

    使用该扩展方法,您的方法可以重写为:

    Task Sequential()
    {
        return A()
            .ContinueWith(t => B()).Unwrap()
            .ContinueWith(t => X()); // You said X() is "non-async", so no Unwrap here.
    }
    

    生成的Task 将代表整个顺序任务链按预期顺序完成。

    还有the concept of "child tasks" 最初是在任务并行库的早期为此目的而设计的,但它非常难以使用,并且需要您对任务的启动方式有很大的控制权 em>,你可能没有。尽管如此,还是值得了解(如果只是为了教育)。

    【讨论】:

      【解决方案3】:

      使用 Microsoft 的响应式框架 (NuGet "System.Reactive") 有一种相当巧妙的方法。

      public Task Sequential()
      {
          return
          (
              from a in Observable.FromAsync(() => A())
              from b in Observable.FromAsync(() => B())
              from c in Observable.Start(() => X())
              select c
          ).ToTask();
      }
      

      如果我们定义方法是这样的:

      public Task A() { return Task.Run(() => { "A".Dump(); Thread.Sleep(1000); "A".Dump(); }); }
      public Task B() { return Task.Run(() => { "B".Dump(); Thread.Sleep(1000); "B".Dump(); }); }
      public void X() { "X".Dump(); Thread.Sleep(1000); "X".Dump(); }
      

      然后运行Sequential().Wait(); 会产生这样的结果:

      一种 一种 乙 乙 X X

      【讨论】:

        【解决方案4】:

        问题“如何链接独立的 C# 任务?”的另一个答案

        '无限任务链'

        Task _lastRegisteredTask = null;
        
        var newTask = new Task(() =>
        {
            // do stuff
        });
        
        if (_lastRegisteredTask != null && !_lastRegisteredTask.IsCompleted)
        {
            _lastRegisteredTask.ContinueWith(antecedent => newTask.Start());
        }
        else
        {
            newTask.Start();
        }
        
        _lastRegisteredTask = newTask;
        

        这在您需要启动任务并且您不知道也不关心它们将执行多少和多快但您确实关心它们应该以 FIFO 方式工作的情况下非常有用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-04
          • 2013-08-20
          • 2016-09-25
          • 2011-01-22
          • 1970-01-01
          相关资源
          最近更新 更多