如何保证在任务完成顺序中继续运行?

时间:2016-06-03 17:36:16

标签: c# asynchronous task-parallel-library task

如果我的代码通过返回表示每个阶段的Task来抽象分阶段的异步操作序列,那么如何确保延续按阶段顺序执行(即任务完成的顺序)?

请注意,这与“不浪费时间等待较慢的任务”不同。在调度中没有竞争条件的情况下需要保证订单。这个更宽松的要求可以通过以下问题的部分答案来解决:

  1. Sort Tasks into order of completition
  2. Is there default way to get first task that finished successfully?
  3. 认为逻辑解决方案是使用自定义TaskScheduler(例如基于SynchronizationContext的一个)附加延续。但是,我无法保证在任务完成时同步执行 schedule 延续。

    在代码中,这可能类似于

    class StagedOperationSource
    {
        public TaskCompletionSource Connect = new TaskCompletionSource();
        public TaskCompletionSource Accept = new TaskCompletionSource();
        public TaskCompletionSource Complete = new TaskCompletionSource();
    }
    class StagedOperation
    {
        public Task Connect, Accept, Complete;
        public StagedOperation(StagedOperationSource source)
        {
            Connect = source.Connect.Task;
            Accept = source.Accept.Task;
            Complete = source.Complete.Task;
        }
    }
    ...
    private StagedOperation InitiateStagedOperation(int opId)
    {
        var source = new StagedOperationSource();
        Task.Run(GetRunnerFromOpId(opId, source));
        return new StagedOperation(source);
    }
    ...
    public RunOperations()
    {
        for (int i=0; i<3; i++)
        {
            var op = InitiateStagedOperation(i);
            op.Connect.ContinueWith(t => Console.WriteLine("{0}: Connected", i));
            op.Accept.ContinueWith(t => Console.WriteLine("{0}: Accepted", i));
            op.Complete.ContinueWith(t => Console.WriteLine("{0}: Completed", i));
        }
    }
    

    应该产生类似于

    的输出
    0: Connected
    1: Connected
    0: Accepted
    2: Connected
    0: Completed
    1: Accepted
    2: Accepted
    2: Completed
    1: Completed
    

    显然,如果前一阶段失败,该示例缺少将异常转发(或取消)后续阶段的详细信息,但这只是一个例子。

2 个答案:

答案 0 :(得分:3)

每个阶段只需await,然后再进入下一个......

public static async Task ProcessStagedOperation(StagedOperation operation, int i)
{
    await operation.Connect;
    Console.WriteLine("{0}: Connected", i);
    await operation.Accept;
    Console.WriteLine("{0}: Accepted", i);
    await operation.Complete;
    Console.WriteLine("{0}: Completed", i);
}

然后,您可以在for循环中调用该方法。

答案 1 :(得分:2)

如果使用TAP(任务异步编程),即asyncawait,则可以使处理流程更加明显。在这种情况下,我将创建一个新方法来封装操作顺序:

public async Task ProcessStagedOperation(StagedOperation op, int i)
{
    await op.Connect;
    Console.WriteLine("{0}: Connected", i);

    await op.Accept;
    Console.WriteLine("{0}: Accepted", i)

    await op.Complete;
    Console.WriteLine("{0}: Completed", i)
}

现在你的处理循环有点简化了:

public async Task RunOperations()
{
    List<Task> pendingOperations = new List<Task>();

    for (int i=0; i<3; i++)
    {
        var op = InitiateStagedOperation(i);
        pendingOperations.Add(ProcessStagedOperation(op, i));
    }

    await Task.WhenAll(pendingOperations); // finish
}

现在,您可以引用可以明确等待的任务对象,或者只是从另一个上下文中await。 (或者你可以简单地忽略它)。我修改RunOperations()方法的方法允许您创建一个大型待处理任务队列,但在等待它们全部完成时不会阻塞。