ContinueWith回调是否会在返回Task前执行?同步任务列表疑问
问题描述
出于监控目的,我维护着一个同步的Task列表,希望逐步移除已完成的任务,为此采用了如下延续任务实现:
//任务结束时移除createdTask Task self = null!; OnGoingHandlers.Add(self = CreatedTask.ContinueWith(tsk => OnGoingHandlers.Remove(self)));
请问是否存在OnGoingHandlers.Remove被调用时self仍为null的可能性?
补充场景
- 使用
HttpListener,监听器会根据接收消息的JSON内容生成一个或多个任务;停止监听器时,需知晓其生成的所有任务何时全部完成。 OnGoingHandlers是SynchronizedCollection<Task>。
补充说明
我已通过修改代码规避问题:
member.OnGoingHandlers.Add(CreatedTask); CreatedTask.ContinueWith(tsk => member.OnGoingHandlers.Remove(CreatedTask), TaskScheduler.Default);
但仍对最初单行写法的执行行为感到好奇。
解答
不存在OnGoingHandlers.Remove被调用时self为null的可能,核心原因如下:
执行顺序与赋值原子性:
主线程中self = CreatedTask.ContinueWith(...)的执行逻辑是:先调用ContinueWith生成延续任务实例,再将该实例赋值给self,最后执行OnGoingHandlers.Add(...)。也就是说,延续任务被加入集合前,self已经完成了赋值,不可能还是初始的null。延续任务的触发时机:
延续任务只会在CreatedTask完成后才会被调度执行。无论CreatedTask是异步执行还是创建时就已完成,延续任务的执行都会晚于主线程中self的赋值和集合添加操作——即使CreatedTask已完成,延续任务也需要等待主线程当前同步代码块执行完毕后才会启动,此时self早已完成赋值。闭包的引用捕获特性:
延续任务的lambda表达式捕获的是self变量的引用,而非赋值时的瞬时值。但由于主线程在延续任务可能执行前就已经完成了self的赋值,lambda执行时,self引用指向的必然是已生成的延续任务实例,不会是null。
不过需要注意,最初的写法虽无null风险,但存在逻辑偏差:你添加到OnGoingHandlers的是延续任务,而非原始的CreatedTask。这会导致停止监听器时,你等待的是延续任务的完成,而非原始业务任务的完成——虽然两者存在依赖关系,但这种监控对象的错位可能在后续逻辑调整时引发问题,这也是你修改后的写法更合理的原因(直接监控原始任务)。
内容的提问来源于stack exchange,提问作者Andrea Bardelli

