嵌套任务和子任务
嵌套任务就是在另一个任务的用户委托中创建的 Task 实例。 子任务是使用 AttachedToParent 选项创建的嵌套任务。 一个任务可以创建任意数量的子任务和/或嵌套任务,该数量仅受系统资源限制。 下面的示例演示一个父任务,该父任务创建一个简单的嵌套任务。
Shared Sub SimpleNestedTask()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(500000)
Console.WriteLine("Nested task completing.")
End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
' Sample output:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
static void SimpleNestedTask()
{
var parent = Task.Factory.StartNew(() =>
{
Console.WriteLine("Outer task executing.");
var child = Task.Factory.StartNew(() =>
{
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});
parent.Wait();
Console.WriteLine("Outer has completed.");
}
/* Sample output:
Outer task executing.
Nested task starting.
Outer has completed.
Nested task completing.
*/
附加的子任务与分离的嵌套任务
子任务与嵌套任务之间最主要的区别是: 嵌套任务实质上独立于父任务或外部任务,而附加的子任务与父任务密切同步。 如果将任务创建语句改为使用 AttachedToParent 选项(如下面的示例所示),
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
var child = Task.Factory.StartNew((t) =>
{
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
将生成以下输出。
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Parent task executing.
Attached child starting.
Attached child completing.
Parent has completed.
您可以使用附加的子任务创建异步操作的紧密同步关系图。 但是,在大多数情况下,因为与其他任务的关系不太复杂,所以建议使用嵌套任务。 这就是默认情况下嵌套其他任务内创建的任务的原因,您必须显式指定 AttachedToParent 选项以创建子任务。
下表列出了两种子任务的基本区别。
类别 |
嵌套任务 |
附加的子任务 |
---|---|---|
外部任务(父任务)等待内部任务完成。 |
否 |
是 |
父任务传播子任务(内部任务)引发的异常。 |
否 |
是 |
父任务(外部任务)的状态依赖于子任务(内部的任务)的状态。 |
否 |
是 |
在嵌套任务为 Task<TResult> 的分离方案中,您仍可以通过访问嵌套任务的 Result 属性来强制父任务等待子任务。 Result 属性处于阻止状态,直到其任务完成。
Shared Sub WaitForSimpleNestedTask()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Nested task completing.")
Return 42
End Function)
Return child.Result
End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
'Sample output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
static void WaitForSimpleNestedTask()
{
var outer = Task<int>.Factory.StartNew(() =>
{
Console.WriteLine("Outer task executing.");
var nested = Task<int>.Factory.StartNew(() =>
{
Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});
// Parent will wait for this detached child.
return nested.Result;
});
Console.WriteLine("Outer has returned {0}.", outer.Result);
}
/* Sample output:
Outer task executing.
Nested task starting.
Nested task completing.
Outer has returned 42.
*/
嵌套任务和子任务中的异常
如果嵌套任务引发异常,必须像任何非嵌套任务一样,在外部任务中直接查看或处理该异常。 如果附加的子任务引发异常,该异常会自动传播到父任务并传回等待或尝试访问该任务的 Result 属性的线程。 因此,通过使用附加的子任务,您仅在一个位置点就可以处理所有异常,即在调用线程上调用 Wait。 有关更多信息,请参见异常处理(任务并行库)。
取消和子任务
请记住,任务取消是协作性的操作。 因此,若要“可取消”,每个附加或分离的子任务必须监视取消标记的状态。 如果要通过使用一个取消请求来取消父任务及其所有子任务,请将同一标记作为参数传递给所有任务,并在每个任务中提供逻辑以响应请求。 有关更多信息,请参见任务取消和如何:取消任务及其子级。
父任务何时取消
如果父任务在子任务启动之前取消自己,则子任务(嵌套任务)将显然永远不会启动。 如果父任务在子任务或嵌套任务启动后取消自己,则嵌套任务(子任务)将运行直到完成,除非它具有自己的取消逻辑。 有关更多信息,请参见任务取消。
嵌套任务何时取消
如果分离的子任务通过使用传递给它的同一标记取消自己,且父任务不等待子任务,则不会传播异常,因为异常被视为良性协作取消。 此行为与任何顶级任务的行为相同。
子任务何时取消
当附加的子任务通过使用传递给它的同一标记取消自己时,TaskCanceledException 传播到 AggregateException 内的联接线程。 等待父任务非常重要,这样,除了可以处理通过附加子任务的关系图向上传播的所有出错异常,还可以处理向上传播的所有良性异常。
有关更多信息,请参见异常处理(任务并行库)。