为什么这个任务早点返回? 我做错了什么吗?

我正在尝试建立一些具有一些最小耦合的工作者,但我想使用C# async和任务。 并非所有任务都是纯粹的异步(有些将是完全同步的)。 这样做的动机是我想创建一些执行业务逻辑的简单方法,并使用System.Threading.Tasks.Task API将它们链接在一起,以保留一些排序概念。 基本上,我想创建第一个任务,注册一些延续,然后等待最终任务完成。

这是我构建的简单原型,看看我想做什么甚至可以工作:

 void Main() { var worker = new Worker(); var work = worker.StartWork(1, 2000); work.ConfigureAwait(false); var final = work.ContinueWith(_ => worker.StartWork(2, 0)) .ContinueWith(ant => worker.StartWork(3, 1500)); var awaiter = final.ContinueWith(_ => Tuple.Create(_.Id, _.Status)); Console.WriteLine("\"final\" completed with result {0}", awaiter.Result); Console.WriteLine("Done."); } // Define other methods and classes here class Worker { internal async Task StartWork(int phase, int delay) { Console.WriteLine("Entering phase {0} (in Task {1}) with {2} milliseconds timeout.", phase, Task.CurrentId, delay); if (delay > 0) { Console.WriteLine("Do wait for {0} milliseconds.", delay); await Task.Delay(delay); } Console.WriteLine("ending phase {0}", phase); } } 

问题似乎在于 等待 等待所谓的awaiter任务:

 Entering phase 1 (in Task ) with 2000 milliseconds timeout. Do wait for 2000 milliseconds. ending phase 1 Entering phase 2 (in Task 769) with 0 milliseconds timeout. ending phase 2 Entering phase 3 (in Task 770) with 1500 milliseconds timeout. Do wait for 1500 milliseconds. "final" completed with result (770, RanToCompletion) Done. ending phase 3 

这只是不支持吗? 我以为我很了解Task API,但很明显我没有。 我认为我可以将其转换为不使用async或任务,并且只是完全同步地执行该方法,但这似乎是一种糟糕的处理方法。 我想要运行的延续并不完全是这样(它们只接受CancellationToken )。 对任务之间的消息没有特别的依赖 – 我只需要保留一些排序的概念。

谢谢。

编辑:我错误地使用了awaiting上面的单词:我知道访问Task.Result是完全同步的。 我很抱歉。

编辑2:我期望发生的是调用ContinueWith(_ => worker.Start(2, 0))会将任务返回到ContinueWith ,并且当我的用户委托返回时,TPL将在内部等待worker.StartWork返回的任务一个任务。 查看ContinueWith的重载列表,这显然是不正确的。 我正在尝试解决的部分原因是如何从安排工作的Main方法等待; 我不想在所有延续完成之前退出。

我使用ContinueWith的动机是我的要求类似于以下内容:

  1. 主方法有三个阶段。
  2. 阶段1创建三个工作人员: abc
  3. 第2阶段在完成任务bc时启动另一个工作人员,在完成ab时启动另一个工作人员(依赖关系是必须按此顺序创建这些任务)
  4. 在完成所有工作之前,将执行与此类似的过程。

如果我正确理解了评论中的反馈,我基本上有两种方法可以做到这一点:

  1. 使方法同步,并自己注册continuation。
  2. 保留标记为async Task的方法,并使用await关键字和Task.WhenAll API来安排延续。

凯文的回答很好。 但根据评论,您似乎相信“继续”在描述工作流程的顺序时会以某种方式为您提供更多的力量。 它不是。 此外,还没有解决如何正确构建第二次编辑工作流程而不诉诸显式延续的问题。

我们来看看你的情景吧。 您的工作流程是:我们有任务A,B,C,D和E.起始D取决于B和C的完成; 开始E取决于A和B的完成。

轻松完成。 请记住: await是对任务的排序操作 。 每当我们想说“Y必须在X之后”时,我们只需在Y开始之前在任何地方等待X. 相反,如果我们不希望某项任务在某事之前被迫完成,我们就不会等待它。

这是一个小小的框架,可以玩; 这可能不是我编写真实代码的方式,但它清楚地说明了工作流程。

  private async Task DoItAsync(string s, int d) { Console.WriteLine($"starting {s}"); await Task.Delay(d * 1000); Console.WriteLine($"ending {s}"); } private async Task DoItAsync(Task pre1, Task pre2, string s, int d) { await pre1; await pre2; await DoItAsync(s, d); } private async void Form1_Load(object sender, EventArgs e) { Task atask = DoItAsync("A", 2); Task btask = DoItAsync("B", 10); Task ctask = DoItAsync("C", 2); Task bcdtask = DoItAsync(btask, ctask, "D", 2); Task abetask = DoItAsync(btask, atask, "E", 2); await bcdtask; await abetask; } 

跟着。 A,B和C开始。 (请记住,它们是异步的。“await”不会使它们异步;等待在工作流程中引入一个排序点。)

接下来我们启动两个辅助任务。 D的前提条件是B和C,所以我们等待B.假设B是不完整的,所以await将一个任务返回给调用者,表示“b和c完成后开始d,等待d完成”的工作流程”。

现在我们开始第二个辅助任务。 再次,它等待B.让我们再次假设它是不完整的。 我们回到来电者那里。

现在我们将最后一点结构添加到我们的工作流程中。 在两个帮助程序任务完成之前,工作流程尚未完成 。 在D和E完成之前,两个辅助任务是不完整的,并且它们甚至不会开始,直到B和C(在D的情况下)或B和A(在E的情况下)完成。

使用这个小框架来解决完成任务的时间,你会发现使用await在工作流中建立依赖关系是非常简单的。 这就是它的用途 。 它被称为等待,因为它异步等待任务完成

我期望发生的是调用ContinueWith(_ => worker.Start(2,0))会将任务返回到ContinueWith,当我的用户委托返回任务时,TPL将在内部等待worker.StartWork返回的任务。 查看ContinueWith的重载列表,这显然是不正确的。

这确实是你错过的。 在你的情况下.ContinueWith将返回一个Task而不仅仅是你预期的Task 。 也就是说,使用Unwrap方法将嵌套任务转换为单个任务可以很容易地解决这个问题:

 var worker = new Worker(); var work = worker.StartWork(1, 2000); var final = work.ContinueWith(_ => worker.StartWork(2, 0)).Unwrap() .ContinueWith(ant => worker.StartWork(3, 1500)).Unwrap(); var awaiter = final.ContinueWith(_ => Tuple.Create(_.Id, _.Status)); Console.WriteLine("\"final\" completed with result {0}", awaiter.Result); Console.WriteLine("Done."); 

这将为您提供所需的输出。 但正如其他人所提到的,你可能想要使用async / await,因为它会使你的代码更容易阅读和遵循(并且会保护你免受一些极端情况的ContinueWith ):

 static async Task DoWork() { var worker = new Worker(); await worker.StartWork(1, 2000); await worker.StartWork(2, 0); await worker.StartWork(3, 1500); } static void Main(string[] args) { DoWork().Wait(); Console.WriteLine("Done."); }