1、Task(任务)和ThreadPool(线程池)不一样html
源码git
一、线程(Thread)是建立并发工具的底层类,可是在前几篇文章中咱们介绍了Thread的特色,和实例。能够很明显发现局限性(返回值很差获取(必须在一个做用域中)),当咱们线程执行完以后不能很好的进行下一次任务的执行,须要屡次销毁和建立,因此不是很容易使用在多并发的状况下。数组
二、线程池(ThreadPool) QueueUserWorkItem是很容易发起并发任务,也解决了上面咱们的须要屡次建立、销毁的性能损耗解决了,可是咱们就是太简单的,我不知道线程何时结束,也没有获取返回值的途径,也是比较尴尬的事情。
安全
三、任务(Task)表示一个经过或不经过线程实现的并发操做,任务是可组合的,使用延续(continuation)可将它们串联在一块儿,它们可使用线程池减小启动延迟,可以使用回调方法避免多个线程同时等待I/O密集操做。多线程
2、初识Task(任务)并发
一、Task(任务)是在.NET 4.0引入的、Task是在咱们线程池ThreadPool上面进行进一步的优化,因此Task默认仍是线程池线程,而且是后台线程,当咱们的主线程结束时其余线程也会结束async
二、Task建立任务,也和以前差很少ide
/// <summary> /// Task 的使用 /// Task 的建立仍是差很少的 /// </summary> public static void Show() { //实例方式 Task task = new Task(() => { Console.WriteLine("无返回参数的委托"); }); //无参有返回值 Task<string> task1 = new Task<string>(() => { return "我是返回值"; }); //有参有返回值 Task<string> task2 = new Task<string>(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("无返回参数的委托"); }); }
说明:函数
一、事实上Task.Factory类型自己就是TaskFactory(任务工厂),而Task.Run(在.NET4.5引入,4.0版本调用的是后者)是Task.Factory.StartNew的简写法,是后者的重载版本,更灵活简单些。工具
二、调用静态Run方法会自动建立Task对象并当即调用Start
三、Task.Run等方式启动任务并无调用Start,由于它建立的是“热”任务,相反“冷”任务的建立是经过Task构造函数。
3、Task(任务进阶)
一、Wait 等待Task线程完成才会执行后续动做
//建立一个线程使用Wait堵塞线程 Task.Run(() => { Console.WriteLine("Wait 等待Task线程完成才会执行后续动做"); }).Wait();
二、WaitAll 等待Task[] 线程数组所有执行成功以后才会执行后续动做
//建立一个装载线程的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAll 执行"); })); } Task.WaitAll(list.ToArray()); Console.WriteLine("Wait执行完毕");
三、WaitAny 等待Task[] 线程数组任一执行成功以后就会执行后续动做
//建立一个装载线程的容器 List<Task> list = new List<Task>(); for (int i = 0; i < 10; i++) { list.Add(Task.Run(() => { Console.WriteLine("WaitAny 执行"); })); } Task.WaitAny(list.ToArray()); Console.WriteLine("WaitAny 执行完毕");
四、WhenAll 等待Task[] 线程数组所有执行成功以后才会执行后续动做、与WaitAll不一样的是他有回调函数ContinueWith
//建立一个装载线程的容器 List<Task> list = new List<Task>(); 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 执行完毕");
五、WhenAny 等待Task[] 线程数组任一执行成功以后就会执行后续动做、与WaitAny不一样的是他有回调函数ContinueWith
//建立一个装载线程的容器 List<Task> list = new List<Task>(); 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();
4、Parallel 并发控制
一、是在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); }); }
5、多线程实例
一、代码异常我信息你们都不陌生,好比我刚刚写代码常常会报 =>对象未定义null 的真的是让我心痛了一地,那咱们的多线程中怎么去处理代码异常呢? 和咱们常常写的同步方法不同,同步方法遇到错误会直接抛出,当是若是咱们的多线程中出现代码异常,那么这个异常会自动传递调用Wait 或者 Task<TResult> 的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(); } }
/// <summary> /// 多线程捕获异常 /// 多线程会将咱们的异常吞了,由于咱们的线程执行会直接执行完代码,不会去等待你捕获到个人异常。 /// 咱们的线程中最好是不要出现异常,本身处理好。 /// </summary> public static void Show() { //建立一个多线程工厂 TaskFactory taskFactory = new TaskFactory(); //建立一个多线程容器 List<Task> tasks = new List<Task>(); //建立委托 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("我已经执行完了"); }
二、多线程取消机制,咱们的Task在外部没法进行暂停 Thread().Abort() 没法很好控制,上上篇中Thread咱们也讲到了Thread().Abort() 的不足之处。有问题就有解决方案。若是咱们使用一个全局的变量控制,就须要不断的监控咱们的变量取消线程。那么说固然有对应的方法啦。CancellationTokenSource (取消标记源)咱们能够建立一个取消标记源,咱们在建立线程的时候传入咱们取消标记源Token。Cancel()方法 取消线程,IsCancellationRequested 返回一个bool值,判断是否是取消了线程了。
/// <summary> /// 多线程取消机制 咱们的Task在外部没法进行暂停 Thread().Abort() 没法很好控制,咱们的线程。 /// 若是咱们使用一个全局的变量控制,就须要不断的监控咱们的变量取消线程。 /// 咱们能够建立一个取消标记源,咱们在建立线程的时候传入咱们取消标记源Token /// Cancel() 取消线程,IsCancellationRequested 返回一个bool值,判断是否是取消了线程了 /// </summary> public static void Show1() { //建立一个取消标记源 CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //建立一个多线程工厂 TaskFactory taskFactory = new TaskFactory(); //建立一个多线程容器 List<Task> tasks = new List<Task>(); //建立委托 Action<object> 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("我已经执行完了"); }
三、多线程建立临时变量,当咱们启动线程以后他们执行没有前后快慢之分,正常的循环中的变量也没有做用。这个时候就要建立一个临时变量存储信息,解决不访问一个数据源。
/// <summary> /// 线程临时变量 /// </summary> public static void Show2() { //建立一个线程工厂 TaskFactory taskFactory = new TaskFactory(); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); //建立一个委托 Action<object> action = x => { Console.WriteLine("传入参数 x:" + x); }; for (int i = 0; i < 20; i++) { //这最主要的就是会建立20个k的临时变量 int k = i; taskFactory.StartNew(action, k); } Console.ReadLine(); }
四、多线程锁,以前咱们有提到过咱们的多线程能够同时公共资源,若是咱们有个变量须要加一,可是和这个时候咱们有10个线程同时操做这个会怎么样呢?
public static List<int> list = new List<int>(); public static int count = 0; public static void Show3() { //建立线程容器 List<Task> tasks = new List<Task>(); 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呢,其实的数据让狗吃了?真的是小朋友有不少问号??????
五、那么咱们要怎么去解决这个问题呢?方法仍是有的今天咱们要将到一个语法糖lock、它能作什么呢?它至关于一个代码块锁,它主要锁的是一个对象,当它锁住对象的时候会当其余线程发生堵塞,由于当它锁住代码时候也是锁住了对象的访问链,是其余的线程不能访问。必须等待对象访问链被释放以后才能被一个线程访问。咱们的使用lock锁代码块的时候,尽可能减小锁入代码块范围,由于咱们锁代码以后会致使只有一个线程能够拿到数据,尽可能只要必须使用lock的地方使用。
六、Lock使用要注意的地方
一、lock只能锁引用类型的对象.
二、不能锁空对象null某一对象能够指向Null,但Null是不须要被释放的。(请参考:认识全面的null)。
三、lock 尽可能不要去锁string 类型虽然它是引用类型,可是string是享元模式,字符串类型被CLR“暂留”
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了全部运行的应用程序域的全部线程中的该文本。所以,只要在应用程序进程中的任何位置处具备相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的全部实例。所以,最好锁定不会被暂留的私有或受保护成员。
四、lock就避免锁定public 类型或不受程序控制的对象。例如,若是该实例能够被公开访问,则 lock(this) 可能会有问题,由于不受控制的代码也可能会锁定该对象。这可能致使死锁,即两个或更多个线程等待释放同一对象。出于一样的缘由,锁定公共数据类型(相比于对象)也可能致使问题。
/// <summary> /// 建立一个静态对象,主要是用于锁代码块,若是是静态的就会全局锁,若是要锁实例类,就不使用静态就行了 /// </summary> private readonly static object obj = new object(); public static List<int> list = new List<int>(); public static int count = 0; /// <summary> /// lock 多线程锁 /// 当咱们的线程访问同一个全局变量、同时访问同一个局部变量、同一个文件夹,就会出现线程不安全 /// 咱们的使用lock锁代码块的时候,尽可能减小锁入代码块范围,由于咱们锁代码以后会致使只有一个线程能够 /// 访问到咱们代码块了 /// </summary> public static void Show3() { //建立线程容器 List<Task> tasks = new List<Task>(); //锁代码 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(); }
七、总结实例篇,双色球实例。
一、双色球:投注号码由6个红色球号码和1个蓝色球号码组成。红色球号码从01--33中选择(不重复)蓝色球号码从01--16中选择(能够跟红球重复),代码我已经实现了你们能够下载源码。只有本身多多倒腾才能让本身的技术成长。 下一次咱们async和await这两个关键字下篇记录