我有两个操作 - 长期运行的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模式。
答案 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开始之前发出取消的情况。