.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 #### 用法五:可以把集合按数字拆分之后在用多线程写入,多线程分批写入,用于需要大量插入数据的情况 比如我要一次性写入1万多条,可能会很慢,影响数据库的性能,还会让请求等待,这个时候就可以把这个待写入的集合根据某个数字拆分成多个子集合,然后开线程来跑这些子集合, 示例代码如下: ``` // 使用多线程来跑 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 9; // 相当于开9个线程,每个线程跑100条 // 根据100条来拆分这个大的集合 List<List<TaskMessage>> subsets = SplitList(taskMessageList, 100); // 按照每个线程处理100条,来处理消息发送 Parallel.ForEach(subsets, parallelOptions, (item) => { taskManageService.AddTaskMessageList(item); }); /// <summary> /// 根据数字拆分集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="list"></param> /// <param name="size"></param> /// <returns></returns> public List<List<T>> SplitList<T>(List<T> list, int size) { List<List<T>> subsets = new List<List<T>>(); for (int i = 0; i < list.Count; i += size) { subsets.Add(list.GetRange(i, size).ToList()); } return subsets; } ```