子任务取消,父任务完成?

Child task canceled, Parent completed?

我正在尝试了解 .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,即使 两个任务共享令牌,并且附加了子项。

如何在发生取消时使父任务 return 状态 Canceled

注意 如果我抛出异常两个任务 return Faulted.

这是 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);

如果您使用此代码做一些有成效的事情,而不仅仅是为了测试,您还应该考虑使用支持 async 委托而不是 Task.Factory.StartNew() 的简化 Task.Run()。这个article很有意思

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

让我们从工作示例开始:

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);
}

为了取消 parent,您需要在 parent token.ThrowIfCancellationRequested() 正文的某处调用。但是,仅调用 token.ThrowIfCancellationRequested() 是不够的。

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

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

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

当附加的子任务取消时

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

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

old one 'wait ON the parent task'.

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

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