导读:本期聚焦于小伙伴创作的《C#中Parallel.Invoke怎么用?如何轻松并行执行多个独立方法》,敬请观看详情。把原本串行的多个独立任务改成并行,往往能让多核机器上的执行时间直接减半,但不少人在C#里只知道开Thread或Task.Run。Parallel.Invoke其实是任务并行库里最省心的写法,它接收一个委托数组,由运行时自动分配到线程池线程上并发执行,并阻塞当前线程直到全部完成。和手动管理Task相比,它内置了负载均衡与工作窃取机制,也无需写await或ContinueWith。不过要注意,传入的方法应当彼此无共享状态依赖,否则会出现数据竞争;若某个方法抛异常,Parallel.Invoke会包装成AggregateException统一抛出。下面从语法、示例到避坑点完整说明其用法。

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

C#中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.InvokeTask.Run + WaitAll
调用方式同步阻塞可异步可同步
负载均衡内置工作窃取依赖线程池默认调度
异常包装AggregateExceptionAggregateException
适用粒度少量独立方法复杂任务组合

如果你的需求仅仅是“同时跑几个独立函数并等它们全结束”,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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。