.NET Core 3.1构建Task<bool>列表:如何避免任务立即执行?
问题描述
我想在.NET Core 3.1中构建一个List<Task<bool>>,里面的任务直到调用Start方法才执行,但现有代码中的任务会被立即执行:
var tasks = new List<Task<bool>>() { subscriptionService.ValidatePurchases(), serviceTwo.Task(), serviceThree.TaskTwo() }; tasks.ForEach(task => { var fieldInfo = typeof(Task).GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic); Delegate action = fieldInfo.GetValue(task) as Delegate; string methodName = action?.Method.Name ?? "null"; try { task.Start(); task.Wait(); if (!task.Result) { Log.Warning($"Scheduled task: ({methodName}) returned false in TaskRunner."); } } catch (Exception ex) { Log.Error(ex, $"Scheduled task: ({methodName}) threw an unhandled exception in TaskRunner."); } });
问题出在哪里?怎么才能维护一个直到调用Start才执行的任务列表?
问题根源
你现在的代码里,当你写subscriptionService.ValidatePurchases()的时候,已经调用了这个方法并触发了任务执行——除非你的方法返回的是一个未启动的“冷Task”(比如用new Task(...)创建的),但大多数情况下(比如async方法),方法调用后返回的是已经在运行的“热Task”,所以后续的task.Start()其实是无效的(热Task调用Start会抛出异常)。
另外,通过反射获取m_action字段非常不可靠,这是.NET的内部实现细节,不同版本可能会变化,完全没必要这么做。
解决方案:存储任务创建逻辑而非Task实例
要实现“延迟执行”,你需要存储的是创建任务的逻辑,而不是已经创建好的Task对象。我们可以用Func<Task<bool>>来保存这些逻辑,直到你主动调用它时才会创建并执行任务。
修改后的代码如下:
// 存储任务创建工厂,而不是直接存储Task var taskFactories = new List<Func<Task<bool>>>() { subscriptionService.ValidatePurchases, // 注意:这里不要加(),只是引用方法 serviceTwo.Task, serviceThree.TaskTwo }; foreach (var taskFactory in taskFactories) { // 直接从Func获取方法名,安全可靠 string methodName = taskFactory.Method.Name; Task<bool> task = null; try { // 此时才调用方法,创建并启动任务(如果是热Task的话) task = taskFactory(); // 如果你确定方法返回的是冷Task(比如用new Task创建的),才需要调用Start // task.Start(); // 用await替代Wait,避免异步环境下的死锁问题 var result = await task; if (!result) { Log.Warning($"Scheduled task: ({methodName}) returned false in TaskRunner."); } } catch (Exception ex) { Log.Error(ex, $"Scheduled task: ({methodName}) threw an unhandled exception in TaskRunner."); } }
关键说明
为什么用
Func<Task<bool>>?
这个委托类型代表“一个无参数、返回Task<bool>的方法”。当你把方法名(不带括号)加入列表时,只是保存了方法的引用,并没有执行它——只有当你调用taskFactory()时,才会真正执行方法并创建任务。热Task vs 冷Task
- 大多数情况下(比如async方法、
Task.Run创建的任务),方法返回的是热Task,调用方法后任务就已经开始执行了,不需要调用Start。 - 如果你的方法确实返回的是冷Task(比如
return new Task<bool>(() => { ... });),那你需要在创建任务后调用task.Start()来启动它。
- 大多数情况下(比如async方法、
用await替代Wait
task.Wait()会阻塞当前线程,在ASP.NET Core等异步环境中容易引发死锁问题。使用await是异步等待的正确方式,不会阻塞线程,也更符合.NET的异步编程规范。
内容的提问来源于stack exchange,提问作者Roy Berris

