关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

云南大王-多线程之旅(Task 任务)

发布时间:2020-04-13 00:00:00
一、Task(任务)和ThreadPool(线程池)不同       源码   1、线程(Thread)是创建并发工具的底层类,但是在前几篇文章中我们介绍了Thread的特点,和实例。可以很明显发现局限性(返回值不好获取(必须在一个作用域中)),当我们线程执行完之后不能很好的进行下一次任务的执行,需要多次销毁和创建,所以不是很容易使用在多并发的情况下。   2、线程池(ThreadPool) QueueUserWorkItem是很容易发起并发任务,也解决了上面我们的需要多次创建、销毁的性能损耗解决了,但是我们就是太简单的,我不知道线程什么时候结束,也没有获取返回值的途径,也是比较尴尬的事情。   3、任务(Task)表示一个通过或不通过线程实现的并发操作,任务是可组合的,使用延续(continuation)可将它们串联在一起,它们可以使用线程池减少启动延迟,可使用回调方法避免多个线程同时等待I/O密集操作。 二、初识Task(任务)   1、Task(任务)是在.NET 4.0引入的、Task是在我们线程池ThreadPool上面进行进一步的优化,所以Task默认还是线程池线程,并且是后台线程,当我们的主线程结束时其他线程也会结束   2、Task创建任务,也和之前差不多 /// /// Task 的使用 /// Task 的创建还是差不多的 /// public static void Show() { //实例方式 Task task = new Task(() => { Console.WriteLine("无返回参数的委托"); }); //无参有返回值 Task task1 = new Task(() => { return "我是返回值"; }); //有参有返回值 Task task2 = new Task(x => { return "返回值 -- " + x.ToString(); }, "我是输入参数"); //开启线程 task2.Start(); //获取返回值 Result会堵塞线程获取返回值 Console.WriteLine(task2.Result); //使用线程工厂创建 无参数无返回值线程 Task.Factory.StartNew(() => { Console.WriteLine("这个是线程工厂创建"); }).Start(); //使用线程工厂创建 有参数有返回值线程 Task.Factory.StartNew(x => { return "返回值 -- " + x.ToString(); ; }, "我是参数"); //直接静态方法运行 Task.Run(() => { Console.WriteLine("无返回参数的委托"); }); } View Code 说明:   1、事实上Task.Factory类型本身就是TaskFactory(任务工厂),而Task.Run(在.NET4.5引入,4.0版本调用的是后者)是Task.Factory.StartNew的简写法,是后者的重载版本,更灵活简单些。   2、调用静态Run方法会自动创建Task对象并立即调用Start   3、Task.Run等方式启动任务并没有调用Start,因为它创建的是“热”任务,相反“冷”任务的创建是通过Task构造函数。 三、Task(任务进阶)   1、Wait 等待Task线程完成才会执行后续动作 //创建一个线程使用Wait堵塞线程 Task.Run(() => { Console.WriteLine("Wait 等待Task线程完成才会执行后续动作"); }).Wait(); View Code   2、WaitAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作 //创建一个装载线程的容器 List list = new List(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAll 执行"); })); } Task.WaitAll(list.ToArray()); Console.WriteLine("Wait执行完毕"); View Code   3、WaitAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作 //创建一个装载线程的容器 List list = new List(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAny 执行"); })); } Task.WaitAny(list.ToArray()); Console.WriteLine("WaitAny 执行完毕"); View Code   4、WhenAll 等待Task[] 线程数组全部执行成功之后才会执行后续动作、与WaitAll不同的是他有回调函数ContinueWith //创建一个装载线程的容器 List list = new List(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WhenAll 执行"); })); } Task.WhenAll(list.ToArray()).ContinueWith(x => { return x.AsyncState; }); Console.WriteLine("WhenAll 执行完毕"); View Code   5、WhenAny 等待Task[] 线程数组任一执行成功之后就会执行后续动作、与WaitAny不同的是他有回调函数ContinueWith //创建一个装载线程的容器 List list = new List(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WhenAny 执行"); })); } Task.WhenAny(list.ToArray()).ContinueWith(x => { return x.AsyncState; }); Console.WriteLine("WhenAny 执行完毕"); Console.ReadLine(); View Code 四、Parallel 并发控制   1、是在Task的基础上做了封装 4.5,使用起来比较简单,如果我们执行100个任务,只能用到10个线程我们就可以使用Parallel并发控制 public static void Show5() { //第一种方法是 Parallel.Invoke(() => { Console.WriteLine("我是线程一号"); }, () => { Console.WriteLine("我是线程二号"); }, () => { Console.WriteLine("我是线程三号"); }); //for 方式创建多线程 Parallel.For(0, 5, x => { Console.WriteLine("这个看名字就知道是for了哈哈 i=" + x); }); //ForEach 方式创建多线程 Parallel.ForEach(new string[] { "0", "1", "2", "3", "4" }, x => Console.WriteLine("这个看名字就知道是ForEach了哈哈 i=" + x)); //这个我们包一层,就不会卡主界面了 Task.Run(() => { //创建线程选项 ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = 3 }; //创建一个并发线程 Parallel.For(0, 5, parallelOptions, x => { Console.WriteLine("限制执行的次数"); }); }).Wait(); Console.WriteLine("**************************************"); //Break Stop 都不推荐用 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0, 40, parallelOptions, (i, state) => { if (i == 20) { Console.WriteLine("线程Break,Parallel结束"); state.Break();//结束Parallel //return;//必须带上 } if (i == 2) { Console.WriteLine("线程Stop,当前任务结束"); state.Stop();//当前这次结束 //return;//必须带上 } Console.WriteLine("我是线程i=" + i); }); } View Code  五、多线程实例   1、代码异常我信息大家都不陌生,比如我刚刚写代码经常会报 =>对象未定义null  的真的是让我心痛了一地,那我们的多线程中怎么去处理代码异常呢? 和我们经常写的同步方法不一样,同步方法遇到错误会直接抛出,当是如果我们的多线程中出现代码异常,那么这个异常会自动传递调用Wait 或者 Task 的Result属性上面。任务的异常会将自动捕获并且抛给调用者,为了确保报告所有的异常,CLR会将异常封装到AggregateExcepiton容器中,这容器是公开了InnerExceptions属性中包含所有捕获的异常,但是如果我们的线程没有等待结束不会获取到异常。 class Program { static void Main(string[] args) { try { Task.Run(() => { throw new Exception("错误"); }).Wait(); } catch (AggregateException axe) { foreach (var item in axe.InnerExceptions) { Console.WriteLine(item.Message); } } Console.ReadKey(); } } View Code /// /// 多线程捕获异常 /// 多线程会将我们的异常吞了,因为我们的线程执行会直接执行完代码,不会去等待你捕获到我的异常。 /// 我们的线程中最好是不要出现异常,自己处理好。 /// public static void Show() { //创建一个多线程工厂 TaskFactory taskFactory = new TaskFactory(); //创建一个多线程容器 List tasks = new List(); //创建委托 Action action = () => { try { string str = "sad"; int num = int.Parse(str); } catch (AggregateException ax) { Console.WriteLine("我是AggregateException 我抓到了异常啦 ax:" + ax); } catch (Exception) { Console.WriteLine("我是线程我已经报错了"); } }; //这个是我们经常需要做的捕获异常 try { //创建10个多线程 for (int i = 0; i < 10; i++) { tasks.Add(taskFactory.StartNew(action)); } Task.WaitAll(tasks.ToArray()); } catch (Exception ex) { Console.WriteLine("异常啦"); } Console.WriteLine("我已经执行完了"); } View Code   2、多线程取消机制,我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,上上篇中Thread我们也讲到了Thread().Abort() 的不足之处。有问题就有解决方案。如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。那么说当然有对应的方法啦。CancellationTokenSource (取消标记源)我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token。Cancel()方法 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了。 /// /// 多线程取消机制 我们的Task在外部无法进行暂停 Thread().Abort() 无法很好控制,我们的线程。 /// 如果我们使用一个全局的变量控制,就需要不断的监控我们的变量取消线程。 /// 我们可以创建一个取消标记源,我们在创建线程的时候传入我们取消标记源Token /// Cancel() 取消线程,IsCancellationRequested 返回一个bool值,判断是不是取消了线程了 /// public static void Show1() { //创建一个取消标记源 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //创建一个多线程工厂 TaskFactory taskFactory = new TaskFactory(); //创建一个多线程容器 List tasks = new List(); //创建委托 Action action = x => { try { //每个线程我等待2秒钟,不然 Thread.Sleep(2000); //判断是不是取消线程了 if (cancellationTokenSource.IsCancellationRequested) { Console.WriteLine("放弃执行后面线程"); return; } if (Convert.ToUInt32(x) == 20) { throw new Exception(string.Format("{0} 执行失败", x)); } Console.WriteLine("我是正常的我在执行"); } catch (AggregateException ax) { Console.WriteLine("我是AggregateException 我抓到了异常啦 ax:" + ax); } catch (Exception ex) { //异常出现取消后面执行的所有线程 cancellationTokenSource.Cancel(); Console.WriteLine("我是线程我已经报错了"); } }; //这个是我们经常需要做的捕获异常 try { //创建10个多线程 for (int i = 0; i < 50; i++) { int k = i; tasks.Add(taskFactory.StartNew(action, k, cancellationTokenSource.Token)); } Task.WaitAll(tasks.ToArray()); } catch (Exception ex) { Console.WriteLine("异常啦"); } Console.WriteLine("我已经执行完了"); } View Code   3、多线程创建临时变量,当我们启动线程之后他们执行没有先后快慢之分,正常的循环中的变量也没有作用。这个时候就要创建一个临时变量存储信息,解决不访问一个数据源。 /// /// 线程临时变量 /// public static void Show2() { //创建一个线程工厂 TaskFactory taskFactory = new TaskFactory(); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //创建一个委托 Action action = x => { Console.WriteLine("传入参数 x:" + x); }; for (int i = 0; i < 20; i++) { //这最主要的就是会创建20个k的临时变量 int k = i; taskFactory.StartNew(action, k); } Console.ReadLine(); } View Code      4、多线程锁,之前我们有提到过我们的多线程可以同时公共资源,如果我们有个变量需要加一,但是和这个时候我们有10个线程同时操作这个会怎么样呢? public static List list = new List(); public static int count = 0; public static void Show3() { //创建线程容器 List tasks = new List(); for (int i = 0; i < 10000; i++) { //添加线程 tasks.Add(Task.Run(() => { list.Add(i); count++; })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("list 行数:" + list.Count + " count 总数:" + count); Console.ReadLine(); }  我们上面的代码本来是count++到10000,但是我们看到结果的时候,我们是不是傻了呀,怎么是不是说好的10000呢,其实的数据让狗吃了?真的是小朋友有很多问号??????     5、那么我们要怎么去解决这个问题呢?方法还是有的今天我们要将到一个语法糖lock、它能做什么呢?它相当于一个代码块锁,它主要锁的是一个对象,当它锁住对象的时候会当其他线程发生堵塞,因为当它锁住代码时候也是锁住了对象的访问链,是其他的线程不能访问。必须等待对象访问链被释放之后才能被一个线程访问。我们的使用lock锁代码块的时候,尽量减少锁入代码块范围,因为我们锁代码之后会导致只有一个线程可以拿到数据,尽量只要必须使用lock的地方使用。   6、Lock使用要注意的地方       1、lock只能锁引用类型的对象.     2、不能锁空对象null某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)。     3、lock 尽量不要去锁string 类型虽然它是引用类型,但是string是享元模式,字符串类型被CLR“暂留”这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。     4、lock就避免锁定public 类型或不受程序控制的对象。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。 /// /// 创建一个静态对象,主要是用于锁代码块,如果是静态的就会全局锁,如果要锁实例类,就不使用静态就好了 /// private readonly static object obj = new object(); public static List list = new List(); public static int count = 0; /// /// lock 多线程锁 /// 当我们的线程访问同一个全局变量、同时访问同一个局部变量、同一个文件夹,就会出现线程不安全 /// 我们的使用lock锁代码块的时候,尽量减少锁入代码块范围,因为我们锁代码之后会导致只有一个线程可以 /// 访问到我们代码块了 /// public static void Show3() { //创建线程容器 List tasks = new List(); //锁代码 for (int i = 0; i < 10000; i++) { //添加线程 tasks.Add(Task.Run(() => { //锁代码 lock (obj) { //这个里面就只会出现一个线程访问,资源。 list.Add(i); count++; } //lock 是一个语法糖,就是下面的代码 Monitor.Enter(obj); Monitor.Exit(obj); })); } Task.WaitAll(tasks.ToArray()); Console.WriteLine("list 行数:" + list.Count + " count 总数:" + count); Console.ReadLine(); } 7、总结实例篇,双色球实例。   1、双色球:投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从01--33中选择(不重复)蓝色球号码从01--16中选择(可以跟红球重复),代码我已经实现了大家可以下载源码。只有自己多多倒腾才能让自己的技术成长。 下一次我们async和await这两个关键字下篇记录

相关阅读

centos7系统中忘记了root管理员账号密码的解决方式公司管理必须的20条军规[参考]云南昆明天猫旗舰店如何开_怎么开_要什么条件云南大王-通俗理解spring源码(三)—— 获取xml的验证模式 云南大王-用户登录 云南大王-【Golang进阶】指针的详细讲解 云南大王-Java 单线程代码ThreadLocal串值问题 云南大王-Java 实例级别的锁和类级别的锁 云南大王-工作流引擎会签,加签,主持人,组长模式专题讲解 云南大王-Android连载5云南大王-NTP对时器(NTP对时服务器)重要性!京准电子科技 云南大王-关于redis单线程的分析 云南大王-CVE云南大王-PHP SESSION反序列化本地样例分析 云南大王-这不就是多线程ThreadPoolExecutor和阻塞队列吗 云南大王-Tomcat AJP 文件包含漏洞(CVE云南大王-讲真,这两款idea插件,能治愈你英语不好的病 云南大王-消息中间件ActiveMQ、RabbitMQ、RocketMQ、ZeroMQ、Kafka如何选型? 云南大王-JVM系列十三(类加载器). 云南大王-Win10安装MySQL8压缩包版 云南大王-初始WebApi(2) 云南大王-初识人工智能(二):机器学习(一):sklearn特征抽取 云南大王-Popup中ListBox的SelectChange事件关闭弹出窗体后主窗体点击无效BUG 云南大王-基础知识记录 云南大王-FastDFS搭建图片服务器 云南大王-git/sourcetree解决本地仓库历史合并到线上仓的历史数据合并问题_refusing to merge unrelated histories 云南大王-js判断字符是否在数组中【转】 云南大王-Python 云南大王-面向对象之多线程(可捎带电梯调度) 云南大王-Python练习题2.5求奇数分之一序列前N项和(存在问题) 云南大王-React 中的前端路由 react云南大王-VSCODE 远程开发树莓派 云南大王-React新闻网站云南大王-vs .net CS0006 C# 未能找到元数据文件 .dll 云南大王-Vue.js 技术揭秘 云南大王-流程控制语句云南大王-Python学习笔记:Python的时间操作(time,datetime,timedelta,calendar) 云南大王-流程控制语句云南大王-golang Gin framework with websocket 云南大王-多重判断if..else嵌套语句 云南大王-用户登录 云南大王-流程控制语句云南大王-密码类 云南大王-Unity2018发布WebGL注意事项总结 云南大王-web系统安全运营之基础云南大王- 流程控制语句云南大王-中型WPF客户端开发项目总结(3.3.4) 云南大王-流程控制语句云南大王-流程控制语句云南大王-流程控制语句云南大王-中型WPF客户端开发项目总结(4) 云南大王-流程控制语句云南大王-ASP.NET Core笔记(4) 云南大王-C# 基础知识系列云南大王-让 .NET 轻松构建中间件模式代码(二) 云南大王-基于 HTML5 WebGL 的 水泥工厂可视化系统 云南大王-.NET Core 3 WPF MVVM框架 Prism系列之导航系统 云南大王-《JavaScript异步编程》精读笔记 云南大王-合理使用CSS框架,加速UI设计进程 云南大王-CLSID 为 {000209FF云南大王-从零基础转行到前端大牛,需要经过哪几个阶段? 云南大王-一个简单的例子看明白 async await Task 云南大王-【目前】宇宙第一IDE Visual Studio 合并压缩css、js扩展组件云南大王-写一个通用的List集合导出excel的通用方法 云南大王-Bootstrap4 按钮组+徽章(Badges)+进度条+分页+列表组 云南大王-Web前端工程师需要学些什么? 云南大王-react嵌套路由 云南大王-【java框架】Struts2(2) 云南大王-javaSE笔记云南大王-.net core 集成 sentry 进行异常报警 云南大王-Java の 四种引用 云南大王-JVM 虚拟机&&类加载(一) 云南大王-使用Fastjson实现JSON与JavaBean之间互相转换 云南大王-Python操作Oracle数据库:cx_Oracle 云南大王-为什么要用内插字符串代替string.format 云南大王-作为字节跳动的面试官,有些话我不得不说! 云南大王-微信公众号自定义菜单与启用服务器配置冲突(图文消息、链接及文本回复) 云南大王-C#队列学习笔记:RabbitMQ延迟队列 云南大王-Disruptor 基础篇 云南大王-C#获取设备(Audio和Video)名称 简单整理 云南大王-基于注解的IOC配置 云南大王-C#调用EnumDevice获取设备信息 云南大王-Jenkins基础系统之更换镜像源 云南大王-Jenkins基础系统之完整的.net项目编译 云南大王-Scala学习系列(二)——环境安装配置 云南大王-WinForm中DataGridView复制选中单元格内容解决方案 云南大王-关键词匹配优化(第0篇)—— 问题和思路 云南大王-ASP.NET CORE WEBAPI文件下载 云南大王-GC垃圾回收器 云南大王-多线程之旅(Task 任务) 云南大王-当模板方法遇到了委托函数,你的代码又可以精简了 云南大王-基于.NetCore3.1搭建项目系列 —— 使用Swagger导出文档 (补充篇) 云南大王-关键词匹配优化(第1篇)—— 测试计算过程 云南大王-原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么 云南大王-Navicat 密码加密算法 云南大王-【WPF学习】第六十六章 支持可视化状态 云南大王-composer安装 windows 云南大王-ASP.NET Core中的Controller 云南大王-HttpClient来自官方的JSON扩展方法 云南大王-Python3标准库:http.cookies HTTP cookie
/template/Home/Zkeys/PC/Static