Task是.NET Framework4.5出现的,线程是基于线程池的,而后提供丰富的api,Thread方法不少很强大,可是太过强大,没有限制。c++
DoSomethingLong方法以下:数据库


/// <summary> /// 一个比较耗时耗资源的私有方法 /// </summary> /// <param name="name"></param> private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
Task的使用:api
{ Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1")); task.Start(); } { Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2")); } { TaskFactory taskFactory = Task.Factory; Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3")); }
若是这样去调用:缓存
ThreadPool.SetMaxThreads(8, 8); for (int i = 0; i < 100; i++) { int k = i; Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); }); }
若是去掉设置最大线程的代码:安全
for (int i = 0; i < 100; i++) { int k = i; Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); }); }
运行结果以下:多线程
ThreadPool.SetMaxThreads(8, 8);闭包
线程池是单例的,全局惟一的,设置后,同时并发的Task只有8个,并且是复用的,Task的线程是源于线程池的,全局的,请不要这样设置。并发
假如我想控制下Task的并发数量,改怎么作?dom
{ Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Sleep以前"); Thread.Sleep(2000);//同步等待--当前线程等待2s 而后继续 Console.WriteLine("在Sleep以后"); stopwatch.Stop(); Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}"); } { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Delay以前"); Task task = Task.Delay(2000) .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); });//异步等待--等待2s后启动新任务 Console.WriteLine("在Delay以后"); stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); }
运行结果以下:异步
若是将最后一个stopwatch注释掉:
{ Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Sleep以前"); Thread.Sleep(2000);//同步等待--当前线程等待2s 而后继续 Console.WriteLine("在Sleep以后"); stopwatch.Stop(); Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}"); } { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Delay以前"); Task task = Task.Delay(2000) .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); });//异步等待--等待2s后启动新任务 Console.WriteLine("在Delay以后"); //stopwatch.Stop(); //Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}"); }
何时用多线程?
任务并发是时候
多线程能干吗?
提高速度,优化用户体验。
好比,如今有一个场景,在公司开会,领导在分配任务,不能并发,由于只能有一个领导在讲话分配任务,当任务分配下去,开发们确实能够同时开始撸代码,这个是能够并发的。
TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", " DBA "))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat")));
如今要求,谁第一个完成,得到红包奖励(ContinueWhenAny);全部完成后,一块儿庆祝下(ContinueWhenAll),将其放入一个List<Task>里面去
TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", " DBA "))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService"))); taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat"))); //谁第一个完成,获取一个红包奖励 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); //项目完成后,一块儿庆祝一下 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一块儿庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
ContinueWhenAny ContinueWhenAll 非阻塞式的回调;并且使用的线程多是新线程,也多是刚完成任务的线程,惟一不多是主线程
//阻塞当前线程,等着任意一个任务完成 Task.WaitAny(taskList.ToArray());//也能够限时等待 Console.WriteLine("准备环境开始部署"); //须要可以等待所有线程完成任务再继续 阻塞当前线程,等着所有任务完成 Task.WaitAll(taskList.ToArray()); Console.WriteLine("5个模块所有完成后,集中点评");
Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操做,阻塞卡界面,是为了并发以及顺序控制,网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,须要等待WaitAll,列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪一个先完成就用哪一个结果,其余的就无论了。
假如说我想控制下Task的并发数量,该怎么作? 20个
List<Task> taskList = new List<Task>(); for (int i = 0; i < 10000; i++) { int k = i; if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20) { Task.WaitAny(taskList.ToArray()); taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); } taskList.Add(Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); })); }
Parallel并发执行多个Action线程,主线程会参与计算---阻塞界面。等于TaskWaitAll+主线程计算
Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"), () => this.DoSomethingLong("btnParallel_Click_2"), () => this.DoSomethingLong("btnParallel_Click_3"), () => this.DoSomethingLong("btnParallel_Click_4"), () => this.DoSomethingLong("btnParallel_Click_5"));
Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}")); ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
有没有办法不阻塞?
Task.Run(() => { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); });
几乎90%以上的多线程场景,以及顺序控制,以上的Task的方法就能够完成,若是你的多线程场景太复杂搞不定,那么请梳理一下你的流程,简化一下。建议最好不要线程嵌套线程,两三次勉强能懂,三层就hold不住了,更多的只能求神。
多线程异常:
try { List<Task> taskList = new List<Task>(); for (int i = 0; i < 100; i++) { string name = $"btnThreadCore_Click_{i}"; taskList.Add(Task.Run(() => { if (name.Equals("btnThreadCore_Click_11")) { throw new Exception("btnThreadCore_Click_11异常"); } else if (name.Equals("btnThreadCore_Click_12")) { throw new Exception("btnThreadCore_Click_12异常"); } else if (name.Equals("btnThreadCore_Click_38")) { throw new Exception("btnThreadCore_Click_38异常"); } Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); })); } //多线程里面抛出的异常,会终结当前线程;可是不会影响别的线程; //那线程异常哪里去了? 被吞了, //假如我想获取异常信息,还须要通知别的线程 Task.WaitAll(taskList.ToArray());//1 能够捕获到线程的异常 } catch (AggregateException aex)//2 须要try-catch-AggregateException { foreach (var exception in aex.InnerExceptions) { Console.WriteLine(exception.Message); } } catch (Exception ex)//能够多catch 先具体再所有 { Console.WriteLine(ex); } //线程异常后常常是须要通知别的线程,而不是等到WaitAll,问题就是要线程取消 //工做中常规建议:多线程的委托里面不容许异常,包一层try-catch,而后记录下来异常信息,完成须要的操做
线程取消:


//多线程并发任务,某个失败后,但愿通知别的线程,都停下来,how? //Thread.Abort--终止线程;向当前线程抛一个异常而后终结任务;线程属于OS资源,可能不会当即停下来 //Task不能外部终止任务,只能本身终止本身(上帝才能战胜本身) //cts有个bool属性IsCancellationRequested 初始化是false //调用Cancel方法后变成true(不能再变回去),能够重复cancel try { CancellationTokenSource cts = new CancellationTokenSource(); List<Task> taskList = new List<Task>(); for (int i = 0; i < 50; i++) { string name = $"btnThreadCore_Click_{i}"; taskList.Add(Task.Run(() => { try { if (!cts.IsCancellationRequested) Console.WriteLine($"This is {name} 开始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(new Random().Next(50, 100)); if (name.Equals("btnThreadCore_Click_11")) { throw new Exception("btnThreadCore_Click_11异常"); } else if (name.Equals("btnThreadCore_Click_12")) { throw new Exception("btnThreadCore_Click_12异常"); } else if (name.Equals("btnThreadCore_Click_13")) { cts.Cancel(); } if (!cts.IsCancellationRequested) { Console.WriteLine($"This is {name}成功结束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); } else { Console.WriteLine($"This is {name}中途中止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); return; } } catch (Exception ex) { Console.WriteLine(ex.Message); cts.Cancel(); } }, cts.Token)); } //1 准备cts 2 try-catch-cancel 3 Action要随时判断IsCancellationRequested //尽快中止,确定有延迟,在判断环节才会结束 Task.WaitAll(taskList.ToArray()); //若是线程还没启动,能不能就别启动了? //1 启动线程传递Token 2 异常抓取 //在Cancel时尚未启动的任务,就不启动了;也是抛异常,cts.Token.ThrowIfCancellationRequested } catch (AggregateException aex) { foreach (var exception in aex.InnerExceptions) { Console.WriteLine(exception.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); }
临时变量:
for (int i = 0; i < 5; i++) { Task.Run(() => { Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); }
为何运行结果后,都是5呢?
临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候,i已是5了
那么该如何解决呢?
每次都声明一个变量k去接收,k是闭包里面的变量,每次循环都有一个独立的k,5个k变量 1个i变量
for (int i = 0; i < 5; i++) { int k = i; Task.Run(() => { Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); }
这样再运行,结果就正常了。
线程安全&lock:
线程安全:若是你的代码在进程中有多个线程同时运行这一段,若是每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的
线程安全问题通常都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改
发生是由于多个线程相同操做,出现了覆盖,怎么解决?
1 Lock解决多线程冲突
Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object,
A不能是Null,能够编译不能运行;
B 不推荐lock(this),外面若是也要用实例,就冲突了
//Test test = new Test(); //Task.Delay(1000).ContinueWith(t => //{ // lock (test) // { // Console.WriteLine("*********Start**********"); // Thread.Sleep(5000); // Console.WriteLine("*********End**********"); // } //}); //test.DoTest(); //C 不该该是string; string在内存分配上是重用的,会冲突 //D Lock里面的代码不要太多,这里是单线程的 Test test = new Test(); string student = "水煮鱼"; Task.Delay(1000).ContinueWith(t => { lock (student) { Console.WriteLine("*********Start**********"); Thread.Sleep(5000); Console.WriteLine("*********End**********"); } }); test.DoTestString(); //2 线程安全集合 //System.Collections.Concurrent.ConcurrentQueue<int> //3 数据分拆,避免多线程操做同一个数据;又安全又高效 for (int i = 0; i < 10000; i++) { this.iNumSync++; } for (int i = 0; i < 10000; i++) { Task.Run(() => { lock (Form_Lock)//任意时刻只有一个线程能进入方法块儿,这不就变成了单线程 { this.iNumAsync++; } }); } for (int i = 0; i < 10000; i++) { int k = i; Task.Run(() => this.iListAsync.Add(k)); } Thread.Sleep(5 * 1000); Console.WriteLine($"iNumSync={this.iNumSync} iNumAsync={this.iNumAsync} listNum={this.iListAsync.Count}"); //iNumSync 和 iNumAsync分别是多少 9981/9988 1到10000之内