.net多线程 , 并行执行Parallel,列表遍历,循环下的并行执行 电脑版发表于:2024/9/4 17:33 Parallel允许线程并行执行。同时支持最大线程执行数量设置,可以设置最大并发数量。 [TOC] ## 基础用法 ``` static void Main(string[] args) { ParallelOptions parallelOptions = new ParallelOptions(); // 表示最大同时支持三个并行方法,也就是同时开三个线程 parallelOptions.MaxDegreeOfParallelism = 3; Parallel.Invoke(parallelOptions, () => { Console.WriteLine("方法1"); }, () => { Console.WriteLine("方法2"); }, () => { Console.WriteLine("方法3"); }); Console.WriteLine("所有并行执行方法结束后执行的"); } ``` 这种用法一般适用于是想要并行执行的方法是比较固定的,比如上面这种固定的三个,比如我们现在遇到的业务场景有个需求就是要请求三个接口,需要请求实验平台、评估平台、考试平台的接口,然后等待三个接口都回来后在根据三个接口返回的数据去综合计算,我们就可以把三个接口请求的代码放到这个并行执行里边,这样三个接口请求的时候不用等待,他们可以一起去执行,不然你需要等待接口1请求回来之后在去请求接口2这样就有点慢了。 ## 循环遍历的时候并行执行 ### for循环 代码如下: ``` static void Main(string[] args) { ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, parallelOptions, index => { // 加个时间暂停能看到三个一组出来 Thread.Sleep(1000); Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> {index}"); }); Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 结束后执行的 "); Console.ReadLine(); } ``` #### 注意不能这样写哦 ``` for (int i = 0; i < 10; i++) { Parallel.Invoke(() => { Console.WriteLine(i); }); } ``` 这样写是错的,这样是循环开启了10个Parallel,不会并行执行的,这样输出还是会从0-9顺序输出的,不应该是开启多个Parallel而是一个Parallel里边多个方法。 **还要注意一个问题,即时在循环遍历的时候这样去写并行任务也是有问题的** ``` for (int i = 0; i < 10; i++) { Task.Run(() => { Console.WriteLine(i); }); } ``` 它不是并行的从0-9开始输出,而是直接全部输出10: <img src="https://img.tnblog.net/arcimg/aojiancc2/cb6883cde4d348a1be205fc76973241f.png" style="width:69px;height:auto;"> ### foreach循环 相比for循环,其实foreach循环用来遍历集合的时候还要方便一点。他里边有很多重构的方法,下面简单的说几个。 #### 用法一:直接使用 ``` static void Main(string[] args) { List<string> list = new List<string>() { "小乔", "大桥", "孙尚香", "貂蝉", "甄宓", "蔡文姬", "杜氏", "邹氏" }; Parallel.ForEach(list, item => { Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> {item}"); }); Console.WriteLine("所有并行执行方法结束后执行的"); Console.ReadLine(); } ``` #### 用法二:增加一点配置的 ``` static void Main(string[] args) { List<string> list = new List<string>() { "小乔", "大桥", "孙尚香", "貂蝉", "甄宓", "蔡文姬", "杜氏", "邹氏","甘夫人" }; ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; // 加上配置的 Parallel.ForEach(list, parallelOptions, item => { // 加个时间暂停能看到三个一组出来 Thread.Sleep(1000); Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> {item}"); }); Console.WriteLine("所有并行方法执行结束后执行的"); Console.ReadLine(); } ``` #### 用法三:foreach也可以直接遍历分组之后的数据 ``` List<LatestClassLesson> latestClassLessons = taskManageService.GetLatestClassLessonNew(); ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 20; // 按照班级来分组处理消息发送 Parallel.ForEach(latestClassLessons.GroupBy(a => a.ClassId), parallelOptions, (item) => { // 获取班级id string classId = item.Key; // 获取班级下的课程id集合,一个班级对应多个课程 List<string> courseList = item.Select(a => a.CourseId).ToList(); // 下面是一大堆逻辑处理 }); ``` 实体也比较简单只有两个字段: ``` public class LatestClassLesson { public string ClassId { get; set; } public string CourseId { get; set; } } ``` #### 用法四:foreach里边也可以用async与await处理异步 ``` // 按照班级来分组处理消息发送 Parallel.ForEach(latestClassLessons.GroupBy(a => a.ClassId), parallelOptions, async (item) => { // 获取班级id string classId = item.Key; // 获取班级下的课程id集合,一个班级对应多个课程 List<string> courseList = item.Select(a => a.CourseId).ToList(); // 下面是一大堆逻辑处理,比如这里列两个异步访问接口的方法 ApiReturnData apiResult = await requestTools.GetAsync("/evaluation/api/Notice/GetStuRankReportByClassID?classId=" + classId); EvalDataDtoWrapp evalDataDtoWrapp = JsonConvert.DeserializeObject<EvalDataDtoWrapp>(apiResult.Data); ApiReturnData prodResult = await requestTools.GetAsync("/prodedu/api/Report/GetCalssProjectRanking?classid=" + classId); ProdDataDtoWrapp prodDataDtoWrapp = JsonConvert.DeserializeObject<ProdDataDtoWrapp>(prodResult.Data); }); ``` .net httpclient 请求可能会报错:One or more errors occurred. (The SSL connection could not be established, see inner exception.) 解决方法参考:https://www.tnblog.net/xiuxin3/article/details/8446