使用Task类实现并行处理时任务委托内方法未被调用问题
嘿,我帮你拆解下这段代码里导致任务执行异常(看起来像停滞)的问题,主要是两个常见的C#坑,咱们一步步来:
1. 循环变量捕获的闭包陷阱
你在Task.Run的委托里直接用了循环变量i,这是个经典的闭包问题——委托捕获的是变量的引用,而不是每次循环时的数值副本。等任务真正开始执行的时候,循环大概率已经跑完了,所以所有任务都会打印最后一次循环的i值(也就是Environment.ProcessorCount),这会让你误以为任务没按预期启动。
修复起来很简单,在循环内部创建一个变量的副本,让委托捕获这个副本就行:
for (int i = 0; i < Environment.ProcessorCount; i++) { // 把当前循环的i值存到局部副本里 int taskId = i; var task = Task.Run(() => { Console.WriteLine("Start the Task " + taskId); // ... 你的其他业务逻辑 }); tasks[i] = task; }
2. 静态构造函数的特性 + 后台线程的终止问题
Task.Run创建的是后台线程,如果你的程序主线程没有其他工作要做,静态构造函数执行完就直接退出了,后台线程会被强制终止,看起来就像是任务“停滞”了没执行。
另外,CLR会保证静态构造函数线程安全、只执行一次,但它本身是同步执行的——启动任务后就直接结束,不会等任务完成。如果需要确保任务执行完毕,或者后续代码依赖任务的执行结果,你得在静态构造函数里等待所有任务完成:
static TestClass() { var tasks = new Task[Environment.ProcessorCount]; for (int i = 0; i < Environment.ProcessorCount; i++) { int taskId = i; var task = Task.Run(() => { Console.WriteLine("Start the Task " + taskId); // ... 你的其他业务逻辑 }); tasks[i] = task; } // 等待所有任务执行完成 Task.WaitAll(tasks); }
不过要提醒你:在静态构造函数里等待任务有死锁风险(比如任务内部又尝试访问TestClass的静态成员),所以更推荐把并行任务逻辑放到专门的静态初始化方法里,这样更灵活:
public static class TestClass { // 手动调用这个方法初始化并行任务 public static void InitializeParallelTasks() { var tasks = new Task[Environment.ProcessorCount]; for (int i = 0; i < Environment.ProcessorCount; i++) { int taskId = i; var task = Task.Run(() => { Console.WriteLine("Start the Task " + taskId); // ... 你的其他业务逻辑 }); tasks[i] = task; } Task.WaitAll(tasks); } }
然后在程序启动的时候调用TestClass.InitializeParallelTasks(),这样你能完全控制任务的执行时机和等待逻辑。
为什么会觉得“停滞”?
如果不等待任务完成,主线程退出后后台线程会立刻被终止,你可能看不到任何输出,或者只看到零星的几条,这就会让你误以为任务停滞在第一个方法调用处。加上Task.WaitAll(tasks)后,主线程会等所有任务跑完,你就能看到所有任务的输出了。
内容的提问来源于stack exchange,提问作者Kavinda Gayashan

