自动重复一个任务,直到另一个任务完成(TAP)

时间:2013-08-05 15:01:12

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

我有两个操作 - 长期运行的OperationA和更快的OperationB。我使用TAP并行运行它们并返回结果:

var taskA = Task.Factory.StartNew(()=>OperationA());
var taskB = Task.Factory.StartNew(()=>OperationB());
var tasks = new Task[] { taskA, taskB };
Task.WaitAll(tasks);
// processing taskA.Result, taskB.Result

这里没有魔力。现在我要做的是在OperationA仍在运行的情况下无限期地完成OperationB。因此,当OperationA完成并且OperationB的最后一次传递完成时,将发生整个过程结束点。我正在寻找某种有效的模式,如果可能的话,不会涉及在while循环中轮询OperationA的状态。期待改进此Pluralsight course或类似内容中提出的WaitAllOneByOne模式。

2 个答案:

答案 0 :(得分:1)

试试这个

// Get cancellation support.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;

// Start off A and set continuation to cancel B when finished.
bool taskAFinished = false;
var taskA = Task.Factory.StartNew(() => OperationA());
Task contA = taskA.ContinueWith(ant => source.Cancel());

// Set off B and in the method perform your loop. Cancellation with be thrown when 
// A has completed.
var taskB = Task.Factory.StartNew(() => OperationB(token), token);
Task contB = taskB.ContinueWith(ant => 
    {
        switch (task.Status)
        {
            // Handle any exceptions to prevent UnobservedTaskException.             
            case TaskStatus.RanToCompletion: 
                // Do stuff.
                break;
            case TaskStatus.Canceled:
                // You know TaskA is finished.
                break;
            case TaskStatus.Faulted:
                // Something bad.
                break;
        }
    });

然后在OperationB方法中,您可以执行循环并在TaskA的补充中包含取消...

private void OperationB(CancellationToken token)
{
    foreach (var v in object)
    {
        ...
        token.ThrowIfCancellationRequested(); // This must be handeled. AggregateException.
    }
}

注意,您可以在TaskA的延续中设置一个bool,而不是使取消变得复杂,并在TaskB的循环中检查这一点 - 这将避免任何关于取消的问题。

我希望这会有所帮助

答案 1 :(得分:0)

采取你的方法作为基础并进行了一些调整:

var source = new CancellationTokenSource();
var token = source.Token;
var taskA = Task.Factory.StartNew(
    () => OperationA()
    );
var taskAFinished = taskA.ContinueWith(antecedent =>
    {
        source.Cancel();
        return antecedent.Result;
    });

var taskB = Task.Factory.StartNew(
    () => OperationB(token), token
    );
var taskBFinished = taskB.ContinueWith(antecedent =>
    {
        switch (antecedent.Status)
        {
            case TaskStatus.RanToCompletion:
            case TaskStatus.Canceled:
                try
                {
                   return ant.Result;
                }
                catch (AggregateException ae)
                {
                   // Operation was canceled before start if OperationA is short
                   return null;
                }
            case TaskStatus.Faulted:
                return null;
        }
        return null;
    });

做了两个延续,返回各个操作的结果,所以我可以等待它们两个完成(尝试仅使用第二个并且它不起作用)。

var tasks = new Task[] { taskAFinished, taskBFinished };
Task.WaitAll(tasks);

第一个是传递先行者的任务结果,第二个是在OperationB中获取此时可用的聚合结果(RanToCompletion和Canceled状态都被认为是正确的进程结束)。 OperationB现在看起来像这样:

public static List<Result> OperationB(CancellationToken token)
{
    var resultsList = new List<Result>();
    while (true)
    {               
        foreach (var op in operations)
        {
            resultsList.Add(op.GetResult();
        }
        if (token.IsCancellationRequested)
        {
            return resultsList;
        }
    }
}

稍微改变一下逻辑 - 现在,OperationB中的所有循环都被视为单个任务,但这比保持原子性更容易,并编写某种协调原语来收集每次运行的结果。如果我真的不关心哪个循环产生哪个结果,这似乎是一个不错的解决方案。如果需要,可以改进以后更灵活的实现(我实际上寻找的是递归链接多个操作 - OperationB本身可能具有较小的重复OperationC内部具有相同的行为,OperationC - 当C处于活动状态时运行的多个OperationD等)。
修改
在taskBFinished中添加了异常处理,以适用于OperationA快速并且在OperationB开始之前发出取消的情况。