总结:视频中对于多线程讲的非常透彻,从线程基础概念—>.net不同版本出现的线程方法—>多线程常出现问题—>双色球项目实践,每个知识点都有代码实操,受益匪浅。附上学习笔记和实操代码。
视频
目录
一、线程、进程概念及优缺点
线程:程序执行的最小单位,任何操作都是由线程完成的,使用同步时,资源一直被此线程占用,所以其他界面干不了,会出现卡界面的现象。
1启动不可控制、2结束不可控
一个耗时(i++)、耗资源(thread.sleep)的方法
二、四种异步等待方式
1.异步控制等待-callback回调
action执行完将asyncResult、"nimen"当成参数传给callback
2.异步控制等待-asyncResult.IsCompleted
3.异步控制等待WaitOne()-信号量
4.异步控制等待endinvoke-拿到异步函数的返回值
5.视频第1节课代码
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace multithread{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Console.WriteLine($"[------------主线程start: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); Console.WriteLine($"[------------主线程end: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); } private void dosomething(string name) { Console.WriteLine($"[****Dosomething {name} start: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}*******]"); long result = 0; for(int i =0; i < 10000000; i++) { result += i; } Thread.Sleep(2000); Console.WriteLine($"[****Dosomething {name} end: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}{result}*******]"); } }}
三、.net1.0基础版的thread对象
1.线程启动
{ //Action action = this.dosomething; //IAsyncResult asyncResult = action.BeginInvoke("cc", null, null); //Action action1 =()=> this.dosomething("CC"); //1.线程启动 thread.Start(); ThreadStart threadStart = () => this.dosomething("cc"); Thread thread = new Thread(threadStart); thread.Start(); //2.线等待程thread.Join(); thread.Join(500);//卡主线程 Console.WriteLine($"等待500ms"); thread.Join(); while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(100);//cpu时间片交出去干其他的事,但是内存还是占用 }
2.thread.join()\thread.sleep()\thread.IsBackfround
thread.join(500),是其他人线程等他500ms,此时有两个线程在工程, 一个在等待500ms,一个是自己在运行
thread.sleep(500),是自己线程自己将cpu时间片交出去干其他的事,但是内存还是占用,休眠500ms
前台线程,当软件闪退时,会吐出日志
四、net 2.0 threadpool
1.线程启动、设置线程池最大线程数
2.manualResetEvent.WaitOne()异步控制等待
{ ManualResetEvent manualResetEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(t => { this.dosomething("cc"); manualResetEvent.Set(); });//接收一个没有返回值的委托 manualResetEvent.WaitOne(); Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); }
waitone()风险,无线程可用会卡住
3.手写异步回调函数
4.带返回值的委托异步调用(法语结构待学习)
五、.net3.0 task
1.task线程启动
2.waitall、waitany都会卡主线程卡界面
waitall、waitany应用场景
3.task.whenall.continuewith()
首先task.whenall不卡主界面,快快的结束了btn_task_click是主线程任务
其次continuewith() ,当tasklist中线程执行后并满足条件时(all\any),直接顺序执行下面的委托,类似回调如“得意的笑”
4.taskfactory.continuewith()
5.设计最多只有11个线程在工作
6.taskfactory检测到哪个线程结束后,返回线程标识
7.task多次嵌套实现不卡主线程
8.两个waitall按顺序执行
如果有两个waitall需要执行但他们在不同线程中,但是又要保证这两次waitall的顺序,解决办法,将第一个waitall加到tasklist中,然后让第二个task的waitall来判断
9.thread.sleep()卡线程 task.delay()不卡线程
10.task线程完成标识
六、.net4.5 parallel
1.parallel启动多线程
2.parallel线程停止
七、threadcore
1.异常处理
主线程出现异常没人管理会导致程序崩溃
try { TaskFactory taskFactory = new TaskFactory(); List < Task > tasklist = new List<Task>(); //异常处理 for(int i =0;i<20; i++) { string name = string.Format($"btn_click_{i}"); Action<object> act = t => {try{ Thread.Sleep(2000); if (t.ToString().Equals("btn_click_11")) { throw new Exception(string.Format($"{t}执行失败")); } if (t.ToString().Equals("btn_click_12")) { throw new Exception(string.Format($"{t}执行失败")); } Console.WriteLine("{0}执行成功", t);}catch (Exception ex){ Console.WriteLine(ex.Message);} }; tasklist.Add(taskFactory.StartNew(act, name)); }; Task.WaitAll(tasklist.ToArray()); } catch (AggregateException aex) { foreach(var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch(Exception ex) { Console.WriteLine(ex.Message); }
2.线程取消
tasklist.Add(taskFactory.StartNew(act, name))时,
tasklist.Add(taskFactory.StartNew(act, name,cts.Token))时,
try { TaskFactory taskFactory = new TaskFactory(); List < Task > tasklist = new List<Task>(); CancellationTokenSource cts = new CancellationTokenSource(); for(int i =0;i<40; i++) { string name = string.Format($"btn_click_{i}"); Action<object> act = t => {try{ Thread.Sleep(2000); if (t.ToString().Equals("btn_click_11")) { throw new Exception(string.Format($"{t}执行失败")); } if (t.ToString().Equals("btn_click_12")) { throw new Exception(string.Format($"{t}执行失败")); } if (cts.IsCancellationRequested) { Console.WriteLine("{0}放弃执行", t); return; } else { Console.WriteLine("{0}执行成功", t); }}catch (Exception ex){ cts.Cancel(); Console.WriteLine(ex.Message);} }; tasklist.Add(taskFactory.StartNew(act, name,cts.Token)); } Task.WaitAll(tasklist.ToArray()); } catch (AggregateException aex) { foreach(var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch(Exception ex) { Console.WriteLine(ex.Message); }
3.多线程临时变量
4.线程安全lock(lock锁的是引用)
4.1线程安全问题
4.2线程共有变量存在线程安全问题
4.3lock锁原理
只有一个线程可以进去,没有并发 ,解决问题但牺牲性能
建议使用微软定义的锁
private static readonly object btn_click_lock = new object();//引用型变量 private int TotalCount = 0; private List<int> IntList = new List<int>(); private void button1_Click(object sender, EventArgs e) { Console.WriteLine($"[------------主线程start: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); TaskFactory taskFactory = new TaskFactory(); //private static readonly object btn_click_lock = new object(); List<Task> tasklist = new List<Task>(); for (int i = 0; i< 10000; i++) { int newI = i; //多线程 tasklist.Add(taskFactory.StartNew(() => { // int m = 3 + 2; lock (btn_click_lock) { this.TotalCount += 1; this.IntList.Add(newI); } })); } Task.WaitAll(tasklist.ToArray()); Console.WriteLine(this.TotalCount); Console.WriteLine(this.IntList.Count()); Console.WriteLine($"[------------主线程end: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); }
八、.net.0 await/async
1.await原理
await后的代码相当于回调,只不过不是自己写的,是编译器状态机自带的。
回调方法continuewith是子线程完成的,但await不仅仅是回调(可子线程可主线程,计算机分配),所以他们又有所不同
1.1小代码测试原理
2.带与不带返回值的async方法
2.1只有asynic
2.2asynic\await成对出现
await 后面是要跟带有返回值的方法
在定义方法时,有了async和await套装后,尽管方法没有return,但此方法也可看成是带返回值的
不使用返回值
使用返回值
2.3t.Wait()与await t区别
2.4await反编译-相当于状态机
3.此知识点代码
private void button1_Click(object sender, EventArgs e) { testshow(); } public static void testshow() { test(); } private async static Task test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); { //NoReturnNoAwait(); NoReturn(); for (int i = 0; i < 10; i++) { Thread.Sleep(300); Console.WriteLine($"main thread task managedthreadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}--i={i}"); } } } private static async void NoReturn() { //主线程执行 Console.WriteLine($"NoReturn sleep before await ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); TaskFactory taskFactory = new TaskFactory(); Task task = taskFactory.StartNew(() => { Console.WriteLine($"NoReturn sleep before ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); Thread.Sleep(300); Console.WriteLine($"NoReturn sleep after ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); }); await task; Console.WriteLine($"NoReturn sleep after await ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); } private static async void NoReturnNoAwait() { //主线程执行 Console.WriteLine($"NoReturnNoAwait sleep before task ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); Task task = Task.Run(() => { Console.WriteLine($"NoReturnNoAwait sleep before ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); Thread.Sleep(300); Console.WriteLine($"NoReturnNoAwait sleep after ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); }); Console.WriteLine($"NoReturnNoAwait sleep after task ,threadid={Thread.CurrentThread.ManagedThreadId.ToString("00")}---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}"); }
九、双色球项目
1.代码
知识点
1.7个独立任务,要多线程计算,启动7个线程后,线程内存while一直计算
2.子线程不能更新界面,所以要this.Updatelbl(lbl, sNumber)实现
3.通过while (this.isgoon)从外面终止7个线程
4.通过lock (llock)对7个子线程进行加锁,避免7个线程同一时刻的值是一样的
5.子线程等待,更新界面 if (!this.isexsit(“00”) && !this.blue.Text.Equals(“00”))
6.所有子线程都执行完后回调taskFactory.ContinueWhenAll(this.tasklist.ToArray(), t => this.ShowResult() );//
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace 双色球{ public partial class Form1 : Form { public Form1() { InitializeComponent(); this.start.Enabled = true; this.stop.Enabled = false; } private string[] rednums = { "01","02","03","04","05","06","07","08","09","10", "11","12","13","14","15","16","17","18","19","20", "21","22","23","24","25","26","27","28","29","30", "31","32","33" }; private string[] bluenums = { "01","02","03","04","05","06","07","08","09","10", "11","12","13","14","15","16" }; private static readonly object llock =new object(); private bool isgoon = true; private List<Task> tasklist = new List<Task>(); private void start_Click(object sender, EventArgs e) { try { this.start.Text = "运行ing"; this.start.Enabled = false; this.isgoon = true; this.tasklist = new List<Task>(); this.blue.Text = "00"; this.label1.Text = "00"; this.label2.Text = "00"; this.label3.Text = "00"; this.label4.Text = "00"; this.label5.Text = "00"; this.label6.Text = "00"; Console.WriteLine(new Random().Next(0, 15)); Console.WriteLine(new Random().Next(0, 15)); Console.WriteLine(new Random().Next(0, 15)); Console.WriteLine(new Random().Next(0, 15)); Thread.Sleep(1000); TaskFactory taskFactory = new TaskFactory(); foreach(var control in this.groupBox1.Controls) { if (control is Label)//标签中 { Label lbl = (Label)control; if (lbl.Name.Contains("blue")) //blue { tasklist.Add( taskFactory.StartNew(() =>{ while (this.isgoon) { int indexNum1 = Getrandombnumlong(0, bluenums.Length); string sNumber = this.bluenums[indexNum1]; // lbl.Text = sNumber; this.Updatelbl(lbl, sNumber); }})); } else//red {tasklist.Add( taskFactory.StartNew(() =>{ while (this.isgoon) { int indexNum1 = Getrandombnumlong(0, this.rednums.Length); string sNumber = this.rednums[indexNum1]; // lbl.Text = sNumber; lock (llock) { if (this.isexsit(sNumber))//加锁防止random出现一样的值 { continue;//重复数据时放弃更新 } this.Updatelbl(lbl, sNumber);//7个线程访问的是内存中同一个string sNumber,加锁防止取到的值是重复的, } }})); } } } //Thread.Sleep(3000);//直接等3000ms让stop开关enable,不靠谱,有可能有的线程因为数据重复,text没有更新 //this.stop.Enabled = true; Task.Run(()=> { while (true) { Thread.Sleep(1000); if (!this.isexsit("00") && !this.blue.Text.Equals("00")) {this.Invoke(new Action(()=>{ this.stop.Enabled = true;//子线程操控不了主线程控件,需要主线程来完成})); break; } } }); taskFactory.ContinueWhenAll(this.tasklist.ToArray(), t => this.ShowResult() );//所有子线程都执行完 } catch(Exception ex) { Console.WriteLine("双色球启动出现异常:{0}",ex.Message); } } private void stop_Click(object sender, EventArgs e) { this.start.Enabled = true; this.stop.Enabled = false; this.isgoon = false; //Task.WaitAll(this.tasklist.ToArray());//主线程等着全部任务完成,子任务还需要主线程干活更新label,所以又是死锁 //this.ShowResult(); } private void ShowResult() { MessageBox.Show(string.Format("本期双色球为:{0} {1} {2} {3} {4} {5} 蓝球{6}" , this.label1.Text , this.label2.Text , this.label3.Text , this.label4.Text , this.label5.Text , this.label6.Text , this.blue.Text )); } //更新界面 private void Updatelbl(Label lbl ,string text) { if (lbl.InvokeRequired) { //这里的this指的是winform窗体主UI线程,让UI线程去更新text值 this.Invoke(new Action(()=>{ //if (this.isgoon) //不延迟,点了就不更新 // { lbl.Text = text; Console.WriteLine($"当前update线程id{Thread.CurrentThread.ManagedThreadId}"); // } })); } else { lbl.Text = text; } } //验证标签中的text是否有相同的值 private bool isexsit(string snumber) { foreach (var control in this.groupBox1.Controls) { if (control is Label)//标签中的 { Label lbl = (Label)control; if (lbl.Name.Contains("label")) { if (lbl.Text.Equals(snumber)) {return true; } } } } return false; } public int Getrandombnumlong(int min , int max) { int indexNum = new Random().Next(min, max); Thread.Sleep(1000); return indexNum; } }}
十、知识点代码汇总
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace multithread{ public class Class1 { private void dosomething(string name) { Console.WriteLine($"[****Dosomething {name} start: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}*******]"); } // //Console.WriteLine($"[------------到这里计算完成: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); private void mainpoints() { //1委托调用 Action<string> action = this.dosomething;//委托带参数 Action action1 = () => this.dosomething("CC");//委托不带参数 action.Invoke("AA");//同步调用委托 action("BB");//同步调用委托 action.BeginInvoke("cc", null, null);//异步调用委托 for (int i = 0; i < 5; i++) { string name = string.Format($"btn_click_{i}"); action.BeginInvoke(name, null, null);//异步多线程 } List<Task> tasklist = new List<Task>(); TaskFactory taskFactory = new TaskFactory(); string name1 = string.Format($"btn_click"); CancellationTokenSource cts = new CancellationTokenSource(); Action<object> act = t => { if (t.ToString().Equals("btn_click_11")) { throw new Exception(string.Format($"{t}执行失败")); } }; tasklist.Add(taskFactory.StartNew(act, name1, cts.Token)); //2.委托执行完后进行回调 //标准写法 AsyncCallback callback = new AsyncCallback(i => Console.WriteLine($"[------------到这里计算完成: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]")); //简单写法 AsyncCallback callback1 = i => { Console.WriteLine($"[------------到这里计算完成: 线程号:({Thread.CurrentThread.ManagedThreadId.ToString("00")})---{System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}-----]"); }; action.BeginInvoke("cc", callback, null); //回调1 -异步action执行完才会执行回调-action.BeginInvoke("cc", callback, "nimen")-1 IAsyncResult asyncResult = null; asyncResult = action.BeginInvoke("cc", callback, "nimen"); //回调1 - 异步action执行完才会执行回调 - action.BeginInvoke("cc", callback, "nimen")-2 action.BeginInvoke("cc", r => { Console.WriteLine("11"); }, "nimen"); //回调2 -异步action执行完才会执行回调-(!asyncResult.IsCompleted) while (!asyncResult.IsCompleted) { Console.WriteLine($"文件上传99.9%.."); } //回调3 -异步action执行完才会执行回调--信号量WaitOne() asyncResult.AsyncWaitHandle.WaitOne();//等待异步任务完成后,才打印计算完成 asyncResult.AsyncWaitHandle.WaitOne(2000);//限时等待 // 回调4 - 异步action执行完才会执行回调 - EndInvoke拿到委托函数的返回值 Func<int> func = () => { return DateTime.Now.Day; }; Console.WriteLine($"func.Invoke()={ func.Invoke()}"); IAsyncResult asyncResult1 = func.BeginInvoke(r => { Console.WriteLine(r.AsyncState); }, "nimen"); Console.WriteLine($"func.EndInvoke={ func.EndInvoke(asyncResult1)}"); //3.thread //3.1.线程启动 thread.Start(); ThreadStart threadStart = () => this.dosomething("cc"); Thread thread = new Thread(threadStart); thread.Start(); //3.2.线等待程thread.Join(); thread.Join(500);//卡主线程 Console.WriteLine($"等待500ms"); thread.Join(); //3.3.thread.ThreadState /thread.IsBackfround while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(100);//cpu时间片交出去干其他的事,但是内存还是占用 } thread.IsBackground = true;//前台线程,当软件闪退时,会吐出日志 //4.threadPool //4.1ThreadPool.GetMaxThreads ThreadPool.QueueUserWorkItem(t => { this.dosomething("cc"); this.dosomething("dd"); });//接收一个没有返回值的委托 ThreadPool.SetMaxThreads(16, 16); ThreadPool.GetMaxThreads(out int Workerthreads, out int completionPortThreads); Console.WriteLine($"Workerthreads:{Workerthreads}+completionPortThreads{completionPortThreads}"); //4.2.manualResetEvent ManualResetEvent manualResetEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(t => { this.dosomething("cc"); manualResetEvent.Set(); });//接收一个没有返回值的委托 manualResetEvent.WaitOne(); Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); //5.task Task.Run(() => { this.dosomething("cc"); Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); }); new Task(() => this.dosomething("33")).Start(); TaskFactory taskFactory1 = Task.Factory; taskFactory.StartNew(() => this.dosomething("44")); //5.1 task.waitall、waitany .ContinueWith taskFactory.ContinueWhenAny List<Task> tasklist1 = new List<Task>(); tasklist.Add(Task.Run(() => this.dosomething("33"))); tasklist.Add(Task.Run(() => this.dosomething("33"))); Task.WaitAny(tasklist.ToArray()); Task.WaitAll(tasklist.ToArray()); Task.WhenAll(tasklist.ToArray()).ContinueWith(t => { Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); }); taskFactory.ContinueWhenAny(tasklist.ToArray(), t => { }); //5.2 task线程完成标识 Task task = new Task(t => { }, "CC"); Console.WriteLine(task.AsyncState);//会打印CC //5.3thread.sleep()卡线程 task.delay()不卡线程 //5.4tasklist线程取消 CancellationTokenSource cts1 = new CancellationTokenSource(); tasklist.Add(taskFactory.StartNew(() => this.dosomething("44"), cts1.Token)); //6.parallel启动多线程 Parallel.Invoke( () => this.dosomething("11"), () => this.dosomething("22"), () => this.dosomething("33") ); //6.1Parallel.For Parallel.For(0, 5, i => this.dosomething(i)); Parallel.ForEach(new string[] { "0","1","2","3"},i=>this.dosomething("11")); //6.2控制并发数量 ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 3; Parallel.For(0,40,parallelOptions,(i,state)=> { if (i==2) { state.Break(); state.Stop(); } }); //7.线程安全lock //private static readonly object btn_click = new object(); int c = 0; tasklist.Add(taskFactory.StartNew(() => { int m = 3 + 2; lock (btn_click) { c += 1; } })); //8.await/asyncawait 后的代码相当于回调,只不过不是自己写的,是编译器状态机自带的。 //回调方法continuewith是子线程完成的,但await不仅仅是回调(可子线程可主线程,计算机分配),所以他们又有所不同 private static async void Async() { await Task.Run(() => { Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); }); Console.WriteLine($"等待ThreadPool.QueueUserWorkItem执行完再执行"); } } }}
来源地址:https://blog.csdn.net/m0_46204224/article/details/131756149