并行编程基础——动态并行
问题
假设当下的并行情况很复杂,并行任务的结构及数量只能从运行时获取的信息中心知晓
解决方案
TPL以Task类型为中心。Parallel类以及PLNQ只不过是出于便利,对强大的Task进行了包装,如果要动态并行,最简单的方法就是直接使用Task类型
void Traverse(Node current)
{
DoExpensiveActionOnNode(current);
if(current.Left!=null)
{
Task.Factory.StartNew(
()=>Traverse(current.left),CancellationToken.None,TaskCreationOption.AttachedToParent,TaskScheduler.Default);
}
if(current.Right!=null)
{
Task.Factory.StartNew(
()=>Traverse(current.Right),CancellationToken.None,TaskCreationOptions.AttachedToParent,TaskScheduler.Default);
}
}
void ProcessTree(Node root)
{
Task task=Task.Factory.StartNew(
()=>Traverse(root),CancellationToken.None,TaskCreationOption.None,TaskScheduler.Default);
task.Wait();
}
AttachedToParent标志确保了针对每个分支的Task与其父节点的Task关联。这样一来,对映射了树节点“父子”关系的Task实例而言,他们之间也形成了“父子”关系。父任务执行委托,然后等待子任务完成,随后子任务抛出的异常会从子任务传到父任务。因此,通过对树根的单一Task调用Wait, ProcessTree就可以等待整棵树的任务。
如果遇到非父子型的情况,那么可以通过任务延续调度任务的执行顺序。延续是独立的额任务,会在原始任务完成时执行。
Task task=Task.Factory.StartNew(()=>
Thread.Sleep(TimeSpan.FromSeconds(2)),CancellationToken.None,TaskCreationOpetions.None,TaskScheduler.Default);
Task continuation=task.ContinueWith(
t=>Trace.WriteLine("Task is done"),
CancellationToken.None,TaskContinuationOptions.None,TaskScheduler.Default);
讨论
对并行处理使用Task完全不同于对异步处理使用Task
在并发编程中,Task类型有两个作用,充当秉性任务或者充当异步任务。并行任务可能会使用阻塞成员,例如Task.Wait、Task.Result、Task.WaitAll、Task.WaitAny.秉性任务也经常使用AttachedToParent来构建任务之间的父子关系,而并行任务本身通过Task.Run或者Task.Factory.StartNew来创建。
异步任务应当避免使用阻塞成员。相比之下,await、Task.WhenAll、Task.WhenAny更适用。