在C#开发中,当我们需要同时执行几个互相之间没有依赖关系的操作时,最直观的做法可能是手动创建多个Task或者线程。但实际上,.NET提供的任务并行库中已经有一个非常简洁的API专门解决这个问题,那就是Parallel.Invoke。它允许我们把多个委托放在一起,由运行时自动调度到多个处理器核心上并行运行,调用线程会一直等待所有方法结束才继续往下走。

Parallel.Invoke的基本语法
Parallel.Invoke位于System.Threading.Tasks命名空间下,最常见的重载形式是接收一个params Action[]数组。也就是说,你可以直接把多个方法名、lambda表达式传进去,它们会以并行方式执行。与Task.Run不同,Parallel.Invoke是同步方法,调用它的线程会参与工作并被阻塞,直到全部Action完成。
下面是一段最简单的使用示例,我们并行执行三个独立的方法:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void MethodA()
{
Thread.Sleep(1000);
Console.WriteLine("方法A执行完毕");
}
static void MethodB()
{
Thread.Sleep(1000);
Console.WriteLine("方法B执行完毕");
}
static void MethodC()
{
Thread.Sleep(1000);
Console.WriteLine("方法C执行完毕");
}
static void Main()
{
Parallel.Invoke(MethodA, MethodB, MethodC);
Console.WriteLine("所有方法均已并行完成");
}
}
在上面代码中,如果串行调用这三个各休眠1秒的方法,总耗时大约3秒;而使用Parallel.Invoke后,在双核或更高核心数的机器上,总耗时通常接近1秒。值得注意的是,Parallel.Invoke并不保证执行顺序,MethodA、MethodB、MethodC谁先跑完取决于线程池调度。
使用ParallelOptions控制并行行为
在某些场景下,我们并不希望所有方法都瞬间铺满线程池,或者需要传递一个取消令牌。这时可以使用接收ParallelOptions的重载。通过MaxDegreeOfParallelism可以限制最大并发数,CancellationToken则支持中途取消。
以下示例限制最多两个方法同时执行,并支持取消:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var cts = new CancellationTokenSource();
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 2,
CancellationToken = cts.Token
};
try
{
Parallel.Invoke(options,
() =>
{
Thread.Sleep(500);
Console.WriteLine("任务1完成");
},
() =>
{
Thread.Sleep(500);
Console.WriteLine("任务2完成");
},
() =>
{
Thread.Sleep(500);
Console.WriteLine("任务3完成");
});
}
catch (OperationCanceledException)
{
Console.WriteLine("并行任务被取消");
}
}
}
设置MaxDegreeOfParallelism为2时,即便传入了三个委托,运行时也会最多同时跑两个,第三个会等前面某个结束后再补位。这种机制在调用外部受限接口或者降低资源占用时非常实用。如果我们在另一个线程调用cts.Cancel(),正在执行的方法应通过检查CancellationToken来主动退出,否则Parallel.Invoke捕获到取消请求后会抛出OperationCanceledException。
异常处理机制
Parallel.Invoke在执行期间如果任意一个委托抛出了异常,它不会立即中断其他正在运行的方法,而是会等所有方法执行完毕后,将全部异常收集到AggregateException中再抛出。这一点和Task.Wait类似,需要我们在外层捕获并处理。
示例代码如下:
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
try
{
Parallel.Invoke(
() => { throw new InvalidOperationException("A出错了"); },
() => { Console.WriteLine("B正常完成"); },
() => { throw new ArgumentException("C参数错误"); });
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine("捕获异常: " + ex.Message);
}
}
}
}
在上面的例子中,B方法仍然会执行并打印,而A和C的异常会被统一包裹。通过遍历AggregateException的InnerExceptions,我们可以分别记录或处理每一个错误。实际项目中,建议对并行块内的每个方法单独加try-catch,这样能更精细地控制哪些任务失败可忽略、哪些必须终止。
与Task.Run的对比及适用场景
很多开发者习惯用Task.Run把方法丢进线程池,再用Task.WaitAll等待。Parallel.Invoke在语义上更偏向“数据无关的多操作并行”,它内部使用了工作窃取队列,能更高效地平衡各线程负载。而Task.Run更灵活,适合需要异步等待、组合链式任务的情况。
简单对比可见下表:
| 特性 | Parallel.Invoke | Task.Run + WaitAll |
|---|---|---|
| 调用方式 | 同步阻塞 | 可异步可同步 |
| 负载均衡 | 内置工作窃取 | 依赖线程池默认调度 |
| 异常包装 | AggregateException | AggregateException |
| 适用粒度 | 少量独立方法 | 复杂任务组合 |
如果你的需求仅仅是“同时跑几个独立函数并等它们全结束”,Parallel.Invoke代码量最少也最不容易出错。但若方法之间需要返回值传递、或者要在UI线程上用async/await,则应选择Task相关API。
常见误区与注意事项
第一个误区是认为Parallel.Invoke一定会加快速度。如果方法本身极轻量,或者机器是单核,并行化的线程管理开销反而可能让总耗时变长。第二个误区是共享变量未加锁,比如多个委托同时往同一个List里Add,会造成数据损坏,应使用ConcurrentBag等线程安全集合。
以下代码演示了错误用法与正确用法:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
class Program
{
static void Wrong()
{
var list = new List<int>();
Parallel.Invoke(
() => { for (int i = 0; i < 1000; i++) list.Add(i); },
() => { for (int i = 0; i < 1000; i++) list.Add(i); });
Console.WriteLine("错误示例计数: " + list.Count);
}
static void Right()
{
var bag = new ConcurrentBag<int>();
Parallel.Invoke(
() => { for (int i = 0; i < 1000; i++) bag.Add(i); },
() => { for (int i = 0; i < 1000; i++) bag.Add(i); });
Console.WriteLine("正确示例计数: " + bag.Count);
}
static void Main()
{
Wrong();
Right();
}
}
在Wrong方法中,List<int>并非线程安全,并行Add可能丢数据或抛异常;Right方法改用ConcurrentBag后结果稳定。此外,Parallel.Invoke内部会占用调用线程去执行部分委托,因此不要在UI线程直接调用重负载版本,以免界面卡顿,可包一层Task.Run再调用。
小结
Parallel.Invoke是C#任务并行库里针对“多个独立方法并行执行”的最直接工具。它通过简单的数组传参、同步等待、自动负载均衡,让多核利用变得容易。掌握ParallelOptions限制并发、理解AggregateException的异常聚合、避开共享状态陷阱,就能在日志上报、批量计算、多文件处理等场景中安全增效。
Parallel.InvokeC#并行编程Task并行库修改时间:2026-08-11 07:03:36