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

PSG*_*Guy 2 c# multithreading asynchronous

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

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

问题似乎在于 等待等待所谓的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
Run Code Online (Sandbox Code Playgroud)

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

谢谢.

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

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

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

  1. 主方法有三个阶段.
  2. 第1阶段创建三个工人:a,b,和c.
  3. 第2阶段开始的额外工作人员d的任务完成后b,并c和另一名工人e完成时ab(依赖的是,这些任务必须按以下顺序创建)
  4. 在完成所有工作之前,将执行与此类似的过程.

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

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

Eri*_*ert 7

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

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

轻松完成.请记住:await是对任务的排序操作.我们要说"Y必须X之后来到"任何时候,我们简单地把一个AWAIT X Y被开始之前的任何地方.相反,如果我们不希望某项任务在某事之前被迫完成,我们就不会等待它.

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

    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;
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

使用这个小框架来解决完成任务的时间,您将看到通过使用在工作流中构建依赖关系非常简单await.这就是它的用途.它被称为await,因为它异步等待任务完成.