C#中线程的使用(2)--Parallel与Task
一、简介
接着上一章,本次随笔接着记录一下Parallel与Task开启线程的方法
二、Parallel的使用
1. Parallel.Invoke()
可实现并行执行所提供的多个操作,可使用Invoke执行多个Action,由于主线程也会参与,所以若是有耗时的计算,则会出现明显卡顿的现象
Parallel.Invoke( () => { Console.WriteLine("hello1"); }, () => { Console.WriteLine("hello2"); }, () => { Console.WriteLine("hello3"); }, () => { Console.WriteLine("hello4"); }, () => { Console.WriteLine("hello5"); });
上述例子是同时启动了5个线程分别去打印hello
2. Parallel.For 与 Parallel.ForEach
Parallel.For(0, 5, t => Console.WriteLine(t);));//打印输出0-4 开启5个线程 List<int> temp = new List<int>() { 1,2,3,45,6 }; Parallel.ForEach(temp, t => Console.WriteLine(t));//打印输出集合中的元素 开启5个线程
3. 控制最大并发数
Parallel可以使用ParallelOptions设置开启线程的数量,最大的并发数为3,当一个线程结束后会立马开启新的线程执行剩余的方法
ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3;//设置并发任务的最大数目 Parallel.For(0, 5, parallelOptions, t => Console.WriteLine(t);)); List<int> temp = new List<int>(){1,2,3,4,5,6}; Parallel.ForEach(temp, parallelOptions, t => Console.WriteLine(t));
三、Task
Task 对象通常以异步方式执行在线程池线程,避免了线程的开启和销毁,减小了资源的消耗,我们可以根据IsCanceled, ,IsCompleted, 和 IsFaulted 属性确定任务的状态
1. 创建任务
使用Wait()方法,可以阻塞主线程,直到该任务结束
//方式1 Task task = new Task(() =>Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"))); task.Start();//开始任务 //方式2 Task task1 = Task.Run(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"))); task1.Wait();//等待task1完成 Console.WriteLine(task1.IsCompleted);//打印是否完成 //方式3 使用工厂创建一个新任务 TaskFactory taskFactory = Task.Factory; taskFactory.StartNew(() =>Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00")));
2. 延续任务或回调
2.1 当任务结束时,想在任务完成后执行其他延续任务则可以使用ContinueWith()方法
Task task = Task.Run(() => Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"))) .ContinueWith(t => Console.WriteLine(t.IsCompleted));
2.2 当有多个任务执行时,想要在一个或者所有任务结束后,继续其他任务的,则可以使用ContinueWhenAny() 和 ContinueWhenAll() 延续任务会重新在使用线程池中空闲线程,不会阻塞当前线程
List<Task> tasksList = new List<Task>(); TaskFactory taskFactory = new TaskFactory(); tasksList.Add(Task.Run(() => { Console.WriteLine("启动任务1"); }));//这里只是打印到控制台 可以添加具体的任务 tasksList.Add(Task.Run(() => { Console.WriteLine("启动任务2"); })); tasksList.Add(Task.Run(() => { Console.WriteLine("启动任务3"); })); tasksList.Add(Task.Run(() => { Console.WriteLine("启动任务4"); })); tasksList.Add(Task.Run(() => { Console.WriteLine("启动任务5"); })); taskFactory.ContinueWhenAny(tasksList.ToArray(), t => {Console.WriteLine($"有一个任务已完成"); });//有一个任务完成时,回执行该延续任务,并且可以拿到当前已完成的任务t tasksList.Add(taskFactory.ContinueWhenAll(tasksList.ToArray(), t => { Console.WriteLine($"所有任务已完成"); }));//当所有任务都完成时,会进入该延续任务
2.3 若需要主线程等待一个或全部任务结束则需要使用到Task.WaitAny()或Task.WaitAll()
int taskIndex = Task.WaitAny(tasksList.ToArray());//当有任何一个任务完成时,不在阻塞主线程,并返回当前已完成任务的索引 Task.WaitAll(tasksList.ToArray());//所有任务都结束后,主线程才会继续向下执行
2.4 假设我们不知到数据存在哪个数据库或者哪个存储区,需要启用多个任务进行查询,并且在查询到了数据之后立马对拿到的数据进行下一步计算时,就需要需要使用到ContinueWhenAny()来创建延续任务,并且返回一个当前延续性任务的Task
byte[] vals = new byte[4];//存放查询到的数据 List<Task> tasksList = new List<Task>();//任务集合 TaskFactory taskFactory = new TaskFactory();//任务工厂 tasksList.Add(taskFactory.StartNew(() => { WriteHomework(2, vals, 0); }));//添加任务1 tasksList.Add(taskFactory.StartNew(() => { WriteHomework(3, vals, 1); }));//添加任务2 tasksList.Add(taskFactory.StartNew(() => { WriteHomework(1, vals, 2); }));//添加任务3 tasksList.Add(taskFactory.StartNew(() => { WriteHomework(4, vals, 3); }));//添加任务4 taskFactory.ContinueWhenAny(tasksList.ToArray(), t => { Console.WriteLine($" 当前已完成任务:{tasksList.IndexOf(t)} 获取到数据:{vals[tasksList.IndexOf(t)]}"); });//创建延续任务,打印出获取的值 已完成的任务会作为参数传递到延续性任务中(t) /// <summary> /// 模拟获取的数据的方法 /// </summary> /// <param name="time">执行方法耗时</param> /// <param name="vals">数据存储区</param> /// <param name="taskindex">当前任务索引</param> private void WriteHomework(int time, byte[] vals, int taskindex) { Thread.Sleep(time * 1000); vals[taskindex] = (byte)(new Random().Next(255)); }