0

我有两个操作 - 长时间运行的 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

这里没有魔法。现在我想做的是在 OperationB 无限期完成时重复 OperationB,以防 OperationA 仍在运行。因此,当 OperationA 完成且 OperationB 的最后一遍完成时,将出现整个过程的结束点。我正在寻找某种有效的模式来做到这一点,如果可能的话,不会涉及在 while 循环中轮询 OperationA 的状态。希望改进此Pluralsight 课程或类似课程中提出的 WaitAllOneByOne 模式。

4

2 回答 2

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 的延续中从 with 中设置一个 bool 并在 TaskB' 循环中检查这一点,而不是使取消复杂化——这将避免任何与取消有关的问题。

我希望这有帮助

于 2013-08-05T15:53:49.453 回答
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);

第一个只是进一步传递前面的任务 Result,第二个在 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 启动之前发出取消的情况。

于 2013-08-06T10:22:36.503 回答