在我们的认知里,调用parent.Wait() 时,会等待它的子线程都结束,才会向下执行。
比如,一个线程A有B、C两个子线程,A.Wait() 是等待 A、B、C都结束,才会向下执行。
然而,最近碰到的问题却跟我想的不一样。
问题表象
可以简化为,在父线程中,创建一个子线程。然后在外部等待父线程结束。如下代码:
var parentTask =Task.Run(()=> { Print("Parent") var subTask = new Task(() => { Print("Sub") }, TaskCreationOptions.AttachedToParent); subTask.Start(); }); parentTask.Wait(); Print("Parent End");
按说,parentTask.Wait()会等待父线程、子线程都结束,才会向下执行。即,期望输出 Parent Sub Parent End
但结果却是,Wait并没有等待子线程结束就向下执行了。 实际输出 Parent ParentEnd Sub
解决方案
把 Task.Run 换成 new Task 或者 Task.Factory.StartNew 即可。如下:
var parentTask =new Task(()=> //把Task.Run替换为 new Task { Print("Parent") var subTask = new Task(() => { Print("Sub") }, TaskCreationOptions.AttachedToParent); subTask.Start(); }); parentTask.Start(); parentTask.Wait(); Print("Parent End");
原因剖析
翻一下 Task.Run 的源码。
这个 DenyChildAttach 很可疑。
再看看它的注释,翻译一下:任何子Task 企图绑定到当前线程上时(以AttachedToParent的形式创建),会被拒绝,子Task将独立运行
在创建线程的源码中,也能看出这个枚举的作用:
当创建子线程时会判断:如果 parent.CreateOption 里指定DenyChildAttach,那么,就不会执行 parent.AddNewChild() 。
总结
使用 Task.Run 创建的 Task,是不允许再把其他线程添加为它的子线程的。因为Task.Run内部指定了 DenyChildAttach。
因此,在我们调用task.Wait时,也就不会等待那个没有被成功添加的子线程结束了。