多线程Thread,线程池ThreadPool
首先我们先增加一个公用方法DoSomethingLong(string name),这个方法下面的举例中都有可能用到
1 #region Private Method 2 /// <summary> 3 /// 一个比较耗时耗资源的私有方法 4 /// </summary> 5 /// <param name="name"></param> 6 private void DoSomethingLong(string name) 7 { 8 Console.WriteLine($"*****DoSomethingLong开始;参数【{name}】;线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】;当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***"); 9 long result = 0; 10 for (int i = 0; i < 1_000_000_000; i++) 11 { 12 result += i; 13 } 14 Console.WriteLine($"*****DoSomethingLong结束;参数【{name}】;线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】;当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")};result:{result}***"); 15 } 16 #endregion
一:线程Thread
Thread类是C#语言对线程对象的一个封装,是.netFramwork1.0的出现的
我们先介绍一下Thread的一些常用方法
1:线程启动的两种方法如下:
{ //一:启动带参数的线程 ParameterizedThreadStart是一个带object的委托,可以传任何类型 ParameterizedThreadStart method = o => this.DoSomethingLong(o.ToString()); Thread thread = new Thread(method); thread.Start("123");//开启线程,执行委托的内容 } { //二:启动无参的线程 ThreadStart method = () => { Thread.Sleep(5000); this.DoSomethingLong("测试"); Thread.Sleep(5000); }; Thread thread = new Thread(method); thread.Start();//开启线程,执行委托的内容 }
2:等待线程完成有两种方法如下:
1 { 2 ThreadStart method = () => 3 { 4 Thread.Sleep(5000); 5 this.DoSomethingLong("测试"); 6 Thread.Sleep(5000); 7 }; 8 Thread thread = new Thread(method); 9 thread.Start();//开启线程,执行委托的内容 10 11 //1:使用线程的ThreadState状态来判断等待 12 while (thread.ThreadState != ThreadState.Stopped) 13 { 14 Thread.Sleep(200);//当前线程休息200ms 15 } 16 17 // 2: Join等待 18 thread.Join();//运行这句代码的线程,等待thread的完成 19 thread.Join(1000);//最多等待1000ms 20 }
ThreadState这个是一个枚举,主要是判断线程的状态,具体参考下图:
3:线程的一些其它方法介绍,具体看代码注释
1 { 2 ThreadStart method = () => 3 { 4 Thread.Sleep(5000); //休息5000毫秒,这个用的比较多 5 this.DoSomethingLong("测试"); 6 Thread.Sleep(5000); 7 }; 8 Thread thread = new Thread(method); 9 thread.Start();//开启线程,执行委托的内容 10 11 //以下方法是线程自带的方法,但是线程是调度操作系统资源的,所以如果对线程胡乱更改,会造成线程混乱,以下方法都不建议使用 12 thread.Suspend();//暂停;方法已过时,不建议使用 13 thread.Resume();//恢复;方法已过时,不建议使用 14 thread.Abort(); //终止线程;线程是计算机资源,程序想停下线程,只能向操作系统通知(线程抛异常),会有延时/不一定能真的停下来 15 Thread.ResetAbort(); //取消终止线程 16 }
4:线程的一些属性介绍,具体看代码注释:
1 { 2 ThreadStart method = () => 3 { 4 Thread.Sleep(5000); 5 this.DoSomethingLong("测试"); 6 Thread.Sleep(5000); 7 }; 8 Thread thread = new Thread(method); 9 thread.Start();//开启线程,执行委托的内容 10 11 //最高优先级:优先执行,但不代表优先完成 甚至说极端情况下,还有意外发生,不能通过这个来控制线程的执行先后顺序 12 thread.Priority = ThreadPriority.Highest; 13 14 thread.IsBackground = false;//默认是false 前台线程,进程关闭,线程需要计算完后才退出 15 //thread.IsBackground = true;//设置为后台线程,关闭进程,线程退出 16 ThreadState threadState = thread.ThreadState; //当前线程的状态,具体如上面的一截图 17 string name = thread.Name;//进程的名字 18 int threadId= Thread.CurrentThread.ManagedThreadId; //线程的Id 19 }
5:之前的异步方法我们都想控制先后顺序,比如:启动子线程执行动作A--不阻塞--A执行完后子线程会执行动作B,然而线程我们也想实现这样的功能,这时候大部分人都会想先等线程执行完,然后再执行下面的方法,如下:
1 private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback) 2 { 3 Thread thread = new Thread(threadStart); 4 thread.Start(); 5 thread.Join();//错了,因为方法被阻塞了 6 actionCallback.Invoke(); 7 }
其实这些都是都会把异步变成了同步了,然后启动线程就没有太多意义了,我们可以修改为如下:
1 private void ThreadWithCallBack(ThreadStart threadStart, Action actionCallback) 2 { 3 4 ThreadStart method = new ThreadStart(() => 5 { 6 threadStart.Invoke(); 7 actionCallback.Invoke(); 8 }); 9 new Thread(method).Start(); 10 }
其实我们上面做的仅仅是把两个有先后顺序的同步方法放在一个异步线程中即可
6:以下方法我们既能实现异步非堵塞,又能获取到最终的计算结果
1 private Func<T> ThreadWithReturn<T>(Func<T> func) 2 { 3 T t = default(T); 4 ThreadStart threadStart = new ThreadStart(() => 5 { 6 t = func.Invoke(); 7 }); 8 Thread thread = new Thread(threadStart); 9 thread.Start(); 10 11 return new Func<T>(() => 12 { 13 thread.Join(); 14 //thread.ThreadState 15 return t; 16 }); 17 }
二:线程池ThreadPool
线程池.NetFramework2.0,如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子,这些都是线程池自动控制的,编程时候无需要特别关注这些,
ThreadPool的线程都是后台线程(是不是后台线程下面详细介绍),这样做主要是:
- 节约资源提升性能
- 还能管控总数量,防止滥用
1:创建线程池
1 ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click1")); 2 ThreadPool.QueueUserWorkItem(o => this.DoSomethingLong("btnThreadPool_Click2"), "wss");
2:线程池的一些方法属性设置,具体详情如下:
1 { 2 ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads); 3 Console.WriteLine($"当前电脑最大workerThreads={workerThreads} 最大异步I/O 线程的最大数目={completionPortThreads}"); 4 5 ThreadPool.GetMinThreads(out int workerThreadsMin, out int completionPortThreadsMin); 6 Console.WriteLine($"当前电脑最小workerThreads={workerThreadsMin} 最大completionPortThreads={completionPortThreadsMin}"); 7 8 //设置的线程池数量是进程全局的, 9 //委托异步调用--Task/Parrallel/async/await 全部都是线程池的线程 10 //直接new Thread不受这个数量限制的(但是会占用线程池的线程数量) 11 ThreadPool.SetMaxThreads(8, 8);//设置的最大值,必须大于CPU核数,否则设置无效 12 ThreadPool.SetMinThreads(2, 2); 13 Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&设置最大最小&&&&&&&&&&&&&&&&&&&&&&&&&&&"); 14 15 ThreadPool.GetMaxThreads(out int workerThreads1, out int completionPortThreads1); 16 Console.WriteLine($"当前电脑最大workerThreads={workerThreads1} 最大completionPortThreads={completionPortThreads1}"); 17 18 ThreadPool.GetMinThreads(out int workerThreadsMin1, out int completionPortThreadsMin1); 19 Console.WriteLine($"当前电脑最大workerThreads={workerThreadsMin1} 最大completionPortThreads={completionPortThreadsMin1}"); 20 }
3:线程池等待
1 { 2 ManualResetEvent mre = new ManualResetEvent(true); 3 4 ThreadPool.QueueUserWorkItem(o => 5 { 6 this.DoSomethingLong("btnThreadPool_Click1"); 7 // mre.Set(); 8 }); 9 10 Console.WriteLine("over1..."); 11 Console.WriteLine("over2..."); 12 Console.WriteLine("over3..."); 13 14 mre.WaitOne(); 15 Console.WriteLine("任务已经完成了。。。"); 16 }
注意:
mre.Set():设置为有信号
初始值为false即为关闭
- 只有mre.Set()设置为true为打开信号---WaitOne就才能通过,即waitOne()下面的代码才会执行
- 如果不进行mre.Set(),则WaitOne就只能等待,下面的代码将不会执行
初始值为true为打开则不需要调用mre.Set() ,waitOne()下面的代码就会直接执行
4:上面我们提到了线程池的最大最小线程数,这个如果线程池中的线程被占用完,则程序将出现将死状态,下面的操作也不执行,也不做任何提示,虽然这是一种极端状态,但是会出现,下面的代码都会出现这种状态
1 { 2 //最大线程设置为8 3 ThreadPool.SetMaxThreads(8, 8); 4 ManualResetEvent mre = new ManualResetEvent(false); 5 for (int i = 0; i < 10; i++) 6 { 7 int k = i; 8 ThreadPool.QueueUserWorkItem(t => 9 { 10 Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId.ToString("00")} show {k}"); 11 if (k == 9) 12 { 13 //第9次设置打开线程池信号,由于线程池里面一共只有8个线程,然后第9个将不会有多余的线程执行打开信号,则"任务全部执行成功"将不会输出 14 mre.Set(); 15 } 16 else 17 { 18 mre.WaitOne(); 19 } 20 }); 21 } 22 if (mre.WaitOne()) 23 { 24 Console.WriteLine("任务全部执行成功!"); 25 } 26 }