儿童任务取消,家长完成了?

时间:2015-02-17 14:47:30

标签: c# task-parallel-library

我正在尝试了解.net任务的行为,当孩子被附加时。

我有以下测试代码:

void Test()
{
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;

    Task child = null;
    var parent = Task.Factory.StartNew(() =>
    {
        child = Task.Factory.StartNew(() =>
        {
            while (!token.IsCancellationRequested)
                Thread.Sleep(100);
            token.ThrowIfCancellationRequested();
        }, token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);
    }, token);

    Thread.Sleep(500);

    Debug.WriteLine("State of parent before cancel is {0}", parent.Status);
    Debug.WriteLine("State of child before cancel is {0}", child.Status);

    tokenSource.Cancel();
    Thread.Sleep(500);

    Debug.WriteLine("State of parent is {0}", parent.Status);
    Debug.WriteLine("State of child is {0}", child.Status);
}

结果是:

State of parent before cancel is WaitingForChildrenToComplete
State of child before cancel is Running
A first chance exception of type 'System.OperationCanceledException' occurred in mscorlib.dll
State of parent is RanToCompletion
State of child is Canceled

尽管如此,父任务状态仍然不是Canceled 两个任务共享令牌,并附加子。

如果发生取消,如何让父任务返回状态Canceled

注意 如果我抛出异常,则两个任务都返回Faulted

3 个答案:

答案 0 :(得分:4)

这是MSDN上所述的预期行为。父任务必须为wait(向下滚动到取消部分)以执行子任务。父任务必须处理所有良性故障(如取消)。

要使您的父任务失败,只需等待并传递令牌:

Task child = null;
var parent = Task.Factory.StartNew(() =>
{
  child = Task.Factory.StartNew(() =>
  {
    while (!token.IsCancellationRequested) Thread.Sleep(100);
    token.ThrowIfCancellationRequested();
  }, token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);

  // This is the magic line.
  child.Wait(token);
}, token);

如果您使用此代码执行高效工作而不仅仅用于测试,则还应考虑使用支持Task.Run()代理而不是async的简化Task.Factory.StartNew()。这个article非常有趣。

答案 1 :(得分:1)

你的例子很复杂,隐藏了直观的行为,你的期望是错误的。

让我们从工作示例开始:

void Test()
{
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;

    Task child = null;
    var parent = Task.Factory.StartNew(() =>
    {
        child = Task.Factory.StartNew(() =>
        {
            while (!token.IsCancellationRequested)
                Thread.Sleep(100);
            token.ThrowIfCancellationRequested();
        }, token, TaskCreationOptions.AttachedToParent, TaskScheduler.Default);

        while (!token.IsCancellationRequested)
            Thread.Sleep(100);
        token.ThrowIfCancellationRequested();
    }, token);

    Thread.Sleep(500);

    Debug.WriteLine("State of parent before cancel is {0}", parent.Status);
    Debug.WriteLine("State of child before cancel is {0}", child.Status);

    tokenSource.Cancel();
    Thread.Sleep(500);

    Debug.WriteLine("State of parent is {0}", parent.Status);
    Debug.WriteLine("State of child is {0}", child.Status);
}

要取消父级,您需要在父级token.ThrowIfCancellationRequested()的主体中调用。但是,仅调用token.ThrowIfCancellationRequested()是不够的。

您需要概念化parentchild执行流程是如何组合在一起的,以便了解您的期望为何错误。

Main thread: ---\------------------------------------[Cancel]-----/
Parent:          \---\-----[Check cancellation]------------------/
Child:                \------------------------------[Cancel]---/

从上图中可以看出,父母在请求取消之前检查取消方式。孩子收到取消信号,因为它基本上等待触发取消。现在,如果你在父母中放置相同的机制,它将收到取消信号,因为在取消信号之前它不会完成它的工作。

答案 2 :(得分:0)

当附加的子任务取消时

文档的standard version指出您需要等待父任务。

当我试图在主线程中等待parentTask.Wait()时-没有错误。

old one“等待父任务”。

当我尝试在parentTask中等待childTask.Wait()然后在主线程中等待parentTask.Wait()时-我遇到了错误。

因此,当前文档有误导性。 另一方面,默认情况下,父任务应等待所有附加的子任务。所以我不明白为什么我应该显式地等待parentTask中的childTask.Wait()来捕获主线程中的TaskCanceledException。