以下是学习笔记:
一、多线程的学习
Thread基础使用-->原理分析-->ThreadPool-->Task(主流)
二、为什么学多线程?
在单核时代,我们写的程序,如果没有多线程,就会经常卡顿,但是并不是说用了多线程程序就绝对不卡顿。因为单核时代,即使你用
多线程,实际上,根本不是并行执行。其实是通过时间片切换,达到“并行”目的。
在多核时代,首先从硬件上,就已经实现了真正的并行执行,但是是不是所有的都能并行执行呢?
三、将同步和异步多线程比较
异步多线程:在一定程度上,能够提升系统的性能。改进用户体验。
【1】同步效果:
【2】异步效果:
【3】同步和异步的代码:
#region 同步任务 private void btnSync1_Click(object sender, EventArgs e) { for (int i = 1; i < 20; i++) { Console.WriteLine(i); Thread.Sleep(200); } } private void btnSync2_Click(object sender, EventArgs e) { for (int i = 1; i < 20; i++) { Console.WriteLine($"-------------------------{i}-----------------------"); Thread.Sleep(400); } } #endregion #region 异步任务 private void btnExecute1_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { for (int i = 1; i < 20; i++) { Console.WriteLine(i); Thread.Sleep(200); } }); thread.IsBackground = true;//设置为后台线程(主程序关闭的时候,后台程序自动关闭的) thread.Start(); } private void btnExecute2_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { for (int i = 1; i < 20; i++) { Console.WriteLine($"-------------------------{i}-----------------------"); Thread.Sleep(400); } }); thread.IsBackground = true; thread.Start(); } #endregion
四、跨线程访问控件
【1】效果展示
【2】代码:
//通过跨线程给控件赋值 private void btnExecute1_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { for (int i = 0; i < 10; i++) { // this.lblResult1.Text = i.ToString();//报错:线程间操作无效,从不是创建控件lblResult1的线程访问它 //因为上面thread单独创建了一个子线程,lblResult1是在主线程中创建的,子线程不能直接操作。 if (this.lblResult1.InvokeRequired)//this.lblResult1.InvokeRequired判断需不需要跨线程来访问,这个判断可以不加的 { this.lblResult1.Invoke( new Action<string>(data => { this.lblResult1.Text = data; }), i.ToString() );//参数1:委托,参数2:委托的参数 Thread.Sleep(300); } } }); thread.IsBackground = true; thread.Start(); } //通过线程给控件赋值 private void btnExecute2_Click(object sender, EventArgs e) { for (int i = 0; i < 10; i++) { this.lblResult2.Text = i.ToString(); Thread.Sleep(300); } } //通过跨线程读取控件的值 private void btnRead_Click(object sender, EventArgs e) { //传统方法 //this.lblV.Text = this.txtV.Text; //如果涉及到一些查询延迟计算的,可以用跨线程 Thread thread = new Thread(() => { this.txtV.Invoke(new Action<string>(data => { this.lblV.Text = data;//将读取的控件值,在其他控件lblV中显示出来 }), this.txtV.Text);//读取的输入:txtV }); thread.IsBackground = true; thread.Start(); }
【3】数据库访问
//访问数据库 private void btnExecute1_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { string classCount = DBUtility.SQLHelper.GetSingleResult("select count(*) from Products").ToString(); this.lblResult1.Invoke(new Action<string>(count => { this.lblResult1.Text = count; }), classCount); }); thread.IsBackground = true; thread.Start(); } private void btnExecute2_Click(object sender, EventArgs e) { } //跨线程访问数据库,并在dgv中展示数据 private void btnGetData_Click(object sender, EventArgs e) { Thread thread = new Thread(() => { DataSet ds = DBUtility.SQLHelper.GetDataSet("select * from ProductInventory;select ProductId,ProductName,Unit from Products"); DataTable dt1 = ds.Tables[0]; DataTable dt2 = ds.Tables[1]; this.dgv1.Invoke(new Action<DataTable>(t => { this.dgv1.DataSource = t; }), dt1); this.dgv2.Invoke(new Action<DataTable>(t => { this.dgv2.DataSource = t; }), dt2); }); thread.IsBackground = true; thread.Start(); }
五、多线程底层观察
六,线程生命周期
【1】演示:
【2】代码
private Thread thread = null; private int counter = 0; //【1】开启 private void btnStart_Click(object sender, EventArgs e) { thread = new Thread(() => { while (true) { try { Thread.Sleep(500); lblInfo.Invoke(new Action(() => { lblInfo.Text += counter++ + ","; })); } catch (Exception ex) { MessageBox.Show(ex.Message + " 异常位置:" + counter++); } } }); thread.Start(); } //暂停(线程挂起),只能暂定正在运行的线程或休眠额线程 private void btnSuspend_Click(object sender, EventArgs e) { if (thread.ThreadState == ThreadState.Running || thread.ThreadState == ThreadState.WaitSleepJoin) { thread.Suspend(); } } //继续(继续已挂起的线程) private void btnResume_Click(object sender, EventArgs e) { if (thread.ThreadState == ThreadState.Suspended ) { thread.Resume(); } } //中断 private void btnInterrupt_Click(object sender, EventArgs e) { thread.Interrupt(); } //终止 private void btnAbort_Click(object sender, EventArgs e) { thread.Abort(); }
【3】等待子线程执行完成
Thread thread = new Thread(new ThreadStart(() => { Thread.Sleep(3000); Console.WriteLine("这个是正在执行的子线程数据......"); })); thread.Start(); thread.Join();//会等待子线程执行完毕后,在执行下面的主线程内容。 Console.WriteLine("这个是主线程的数据...");
七,线程池
当我们开发中,用的线程比较多的时候,就要考虑性能问题。所以,我们可以开几个线程,方便使用。
【1】线程池的基本使用
#region 线程池ThreadPool的基本使用 static void Method11() { ThreadPool.QueueUserWorkItem((arg) => { //请在这里编写实际的要处理的内容... Console.WriteLine("Method11子线程Id:" + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("Method11主线程Id:" + Thread.CurrentThread.ManagedThreadId); } //给线程传参数 static void Method12() { ThreadPool.QueueUserWorkItem((arg) => { //请在这里编写实际的要处理的内容... Console.WriteLine("Method12子线程Id:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("arg=" + arg); }, "arg具体的参数"); Console.WriteLine("Method12主线程Id:" + Thread.CurrentThread.ManagedThreadId); } #endregion
【2】线程和ThreadPool和Thread性能比较
#region 线程和ThreadPool和Thread性能比较 static void Method2() { for (int i = 1; i <= 10; i++) { Thread thread = new Thread(() => { Console.WriteLine($"子线程{i} Id:" + Thread.CurrentThread.ManagedThreadId); for (int a = 1; a <= 5; a++) { Console.WriteLine(a); } }); thread.Name = "线程名称:" + i; thread.IsBackground = true; thread.Start(); Thread.Sleep(50); } } static void Method3() { Console.WriteLine("------------------------使用线程池----------------------------"); for (int i = 1; i <= 10; i++) { ThreadPool.QueueUserWorkItem((arg) => { Console.WriteLine($"子线程{i} Id:" + Thread.CurrentThread.ManagedThreadId); for (int a = 1; a <= 5; a++) { Console.WriteLine(a); } }); Thread.Sleep(50); } } #endregion
用Thread的结果:
子线程1 Id:3 1 2 3 4 5 子线程2 Id:4 1 2 3 4 5 子线程3 Id:5 1 2 3 4 5 子线程4 Id:6 1 2 3 4 5 子线程5 Id:7 1 2 3 4 5 子线程6 Id:8 1 2 3 4 5 子线程7 Id:9 1 2 3 4 5 子线程8 Id:10 1 2 3 4 5 子线程9 Id:11 1 2 3 4 5 子线程10 Id:12 1 2 3 4 5
用线程池ThreadPool的结果(下面展示的就是开了2个线程,最多的结果开了4个线程)
------------------------使用线程池---------------------------- 子线程1 Id:3 1 2 3 4 5 子线程2 Id:3 1 2 3 4 5 子线程3 Id:4 1 2 3 4 5 子线程4 Id:3 1 2 3 4 5 子线程5 Id:4 1 2 3 4 5 子线程6 Id:3 1 2 3 4 5 子线程7 Id:4 1 2 3 4 5 子线程8 Id:3 1 2 3 4 5 子线程9 Id:4 1 2 3 4 5 子线程10 Id:3 1 2 3 4 5
【3】分析上面使用线程池为什么最多就开了4个线程
因为电脑里是4核的,4核从效率上来讲是最优的。以后开发过程中能使用线程池的就用线程池
【4】任务管理说明:
内核数是4,逻辑处理器是4,那这个CPU就是真4核,如果这里的内核数是2,逻辑处理器是4,那就是假4核了,实际上是双核四线程。
有人会说,这里看到的CPU有几个框就代表几核,其实这是不准确的,这里的框是线程,不是核,如果是一个双核四线程的CPU,这里显示的就是四个框,那不能说这是个四核CPU,如果非要说四核,那也是假四核了。