C#多线程Thread
Thread .net framework1.0提出的。Thread:是C#对计算机资源线程操作的一个封装类
启动线程的几种方式
可以通过提供委托来启动线程,该委托表示线程在其类构造函数中执行的方法。 然后调用 Start方法开始执行。
线程执行的方法无参数
如果该方法没有参数,则将委托传递给ThreadStart 构造函数。
ThreadStart的签名:
public delegate void ThreadStart()
private static void ThreadStart1() { ThreadStart threadStart = () => { Console.WriteLine("ThreadStartMethod begin...."); }; Thread thread = new Thread(threadStart); thread.Start();//开启一个线程 Console.WriteLine($"ThreadStart线程ID为:{ thread.ManagedThreadId}"); }
线程执行的方法带参数
如果该方法具有参数,则将委托传递 ParameterizedThreadStart给构造函数。
它具有签名:
public delegate void ParameterizedThreadStart(object obj)
/// <summary> /// 带一个参数【Lambda形式的委托】 /// </summary> private static void ParameterizedThreadStart1() { //ParameterizedThreadStart是一个带一个形参的委托 ParameterizedThreadStart method = s => { Console.WriteLine($"ParameterizedThreadStart1传递的参数为{s.ToString()}"); Console.WriteLine($"ParameterizedThreadStart1 thread id={Thread.CurrentThread.ManagedThreadId}"); }; Thread thread = new Thread(method); thread.Start("weiyin1"); } /// <summary> /// 带一个参数[实例化ParameterizedThreadStart委托,委托本质是一个类] /// </summary> private static void ParameterizedThreadStart2() { ParameterizedThreadStart method = new ParameterizedThreadStart(DoSomeThing); Thread thread = new Thread(method); thread.Start("weiyin2"); }
private static void DoSomeThing(object methodname) { Thread.Sleep(1000); Console.WriteLine($"{DateTime.Now.ToString()} method: {methodname},ThreadID=[{Thread.CurrentThread.ManagedThreadId}]"); }
static void Main(string[] args) { ThreadStart1(); ParameterizedThreadStart1(); ParameterizedThreadStart2(); Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}"); }
Thead线程等待、回调
Thread提供了一系列的方法来操作执行的线程,如下所示:
//thread.Suspend();//暂停线程,微软已弃用 //thread.Resume();//恢复线程,微软已弃用,无法实时的暂停或者恢复线程 //thread.Abort();//终结线程 抛出ThreadAbortException异常,以开始终止此线程的过程 //Thread.ResetAbort();//都会有延时
同时也提供了线程等待的方法,如下:
////如果我们需要线程等待: //1.判断状态等待 //while (thread.ThreadState != ThreadState.Stopped) //{ // Thread.Sleep(200); //} ////2.john等待 //thread.Join();//主线程等待子线程计算完成,卡界面。 ////可以限时等待 //thread.Join(2000);//可以限时等待,等待2s,过时不候 //thread.Priority = ThreadPriority.Highest; ////是不是就可以保证是优先执行呢?不能,其只是提供优先执行的概率,优先执行并不代表优先结束,千万不要用这个来控制线程执行的顺序;
如果我们想要控制线程执行顺序,上面给的thread.Priority = ThreadPriority.Highest;其实不靠谱,那如何实现了?
上篇博客我们提到了回调【一个动作执行完后,顺序执行另一个动作】,此处一样的道理。
1.通过while循环判断子线程的执行状态,当其状态为Stopped时(即子线程执行完毕了)后,执行另一个委托绑定的方法【此处为方便,都写的Lambda表达式】。
此方法虽然可以确保两个委托绑定的方法顺序执行,但是第一个委托执行时,主线程一直在判断其状态,处于卡起的状态,且actionCallBack委托Invoke时线程为主线程。
static void Main(string[] args) { Console.WriteLine($"{DateTime.Now.ToString()} 当前主线程ID为:{Thread.CurrentThread.ManagedThreadId}"); ThreadStart threadStart = () => { Console.WriteLine("threadStart委托开始执行"); Console.WriteLine($"{DateTime.Now.ToString()} 当前threadStart线程ID为:{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("threadStart委托执行完毕"); }; Action CallBackaction = () => { Console.WriteLine("CallBackaction委托开始执行"); Console.WriteLine($"{DateTime.Now.ToString()} 当前CallBackaction线程ID为:{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("CallBackaction委托执行完毕"); }; ThreadWithCallBack(threadStart, CallBackaction);
private static void ThreadWithCallBack(ThreadStart threadStart, Action actionCallBack)
{
Thread thread = new Thread(threadStart); thread.Start(); { //主线程会一直while无限循环判断thread子线程的ThreadState状态(卡界面),等待thread子线程其执行完后,在执行后面的动作。 while (thread.ThreadState != ThreadState.Stopped) { Thread.Sleep(1000); } actionCallBack.Invoke(); }
}
2.采用join进行判断,与上面的类似,其也会卡顿主界面。
Thread thread = new Thread(threadStart); thread.Start(); thread.Join();//等待thread子线程执行完毕,其会卡界面 actionCallBack.Invoke();
3 用一个新的委托将这两个顺序执行的委托包起来,然后启动新的线程执行这个新的委托,实现其与主线程异步,但是内部两个委托执行按顺序同步。
//把形参的threadStart和actionCallBack又包了一层。。ThreadStart是一个无参无返回值的委托 //threadStart与actionCallBack的执行是按照先threadStart,后actionCallBack开始的,因为他们包在了 //threadStart1里面是顺序同步的,同时threadStart1子线程与主线程是异步的。实现子线程内部同步按照指定顺序,主线程外面与子线程异步【即不卡界面】,。 //多播委托也可 ThreadStart threadStart1 = new ThreadStart(() => { threadStart.Invoke(); actionCallBack.Invoke(); } ); Thread thread = new Thread(threadStart1); thread.Start();
以上都是无返回值的委托,那如果是执行有返回值的委托呢?
private static void Test() { Func<int> func = () => { return DateTime.Now.Year; }; //int iresult = ThreadWithReturn(func);//能得到2020吗? Func<int> funcreuslt = ThreadWithReturn<int>(func);//不卡界面 { Console.WriteLine("*******"); Console.WriteLine("这里的执行也需要3秒钟"); } int iresult = funcreuslt.Invoke();//这里会卡界面 }
private static Func<T> ThreadWithReturn<T>(Func<T> func) { T t = default(T); ThreadStart threadStart = new ThreadStart(() => { t = func.Invoke(); }); Thread thread = new Thread(threadStart); thread.Start();//不卡界面 Console.WriteLine("子线程ID为" + thread.ManagedThreadId); return new Func<T>( () => { thread.Join();// //此处这里写就是想必须等到子线程执行完毕后,才返回拿结果;有可能在这里都不用等待,上面的子线程start都执行完了 Console.WriteLine("Func线程ID为" + Thread.CurrentThread.ManagedThreadId); return t; }); }
注意此处ThreadWithReturn方法的返回值里面:thread.join,其是为了确保thread子线程执行完毕,而进行等待。
结果为:
因为funcreuslt.Invoke()是在主线程的最后面执行的,所以里面委托绑定的方法也是在前面的打印信息之后执行。
Thead线程前台、后台线程
https://www.cnblogs.com/ryanzheng/p/10961777.html
1、当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALSE的。
2、当主线程退出的时候,IsBackground=FALSE的线程还会继续执行下去,直到线程执行结束。
3、只有IsBackground=TRUE的线程才会随着主线程的退出而退出。
4、当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的退出而退出。
5、原理:只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序。
下面是MSDN给的例子:
例子中创建了两个线程,foregroundThread【IsBackground=false】和backgroundThread [IsBackground=true],foregroundThread线程执行了RunLoop循环打印10次,Background打印50次,当foreground线程执行完后,主线程也执行完毕后,由于设置的backgroundThread
为后台线程,其会随着主线程的退出而退出,而不会继续去执行剩下的逻辑(打印其余次数操作)。
using System; using System.Threading; class Example { static void Main() { BackgroundTest shortTest = new BackgroundTest(10); Thread foregroundThread = new Thread(new ThreadStart(shortTest.RunLoop)); BackgroundTest longTest = new BackgroundTest(50); Thread backgroundThread = new Thread(new ThreadStart(longTest.RunLoop)); backgroundThread.IsBackground = true; foregroundThread.Start(); backgroundThread.Start(); } } class BackgroundTest { int maxIterations; public BackgroundTest(int maxIterations) { this.maxIterations = maxIterations; } public void RunLoop() { for (int i = 0; i < maxIterations; i++) { Console.WriteLine("{0} count: {1}", Thread.CurrentThread.IsBackground ? "Background Thread" : "Foreground Thread", i); Thread.Sleep(250); } Console.WriteLine("{0} finished counting.", Thread.CurrentThread.IsBackground ? "Background Thread" : "Foreground Thread"); } } // The example displays output like the following: // Foreground Thread count: 0 // Background Thread count: 0 // Background Thread count: 1 // Foreground Thread count: 1 // Foreground Thread count: 2 // Background Thread count: 2 // Foreground Thread count: 3 // Background Thread count: 3 // Background Thread count: 4 // Foreground Thread count: 4 // Foreground Thread count: 5 // Background Thread count: 5 // Foreground Thread count: 6 // Background Thread count: 6 // Background Thread count: 7 // Foreground Thread count: 7 // Background Thread count: 8 // Foreground Thread count: 8 // Foreground Thread count: 9 // Background Thread count: 9 // Background Thread count: 10 // Foreground Thread count: 10 // Background Thread count: 11 // Foreground Thread finished counting.
TheadPool线程池
线程池,是在.net framework 2.0提出的,基于Thread做了个升级,Thread功能强大,但是让开发者用不好Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住;
池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子里面获取;用完之后不用销毁,放回池子。(享元模式)其节约资源提升性能;此外,还能管控总数量,防止滥用。
TheadPool线程池的使用、线程设置
通过将waitCallback委托对象加入到ThreadPool.QueueUserWorkItem来启动一个线程(线程来自于线程池)
/// <summary> /// 线程池 /// 在.net framework 2.0基于Thread做了个升级 /// Thread功能强大,但是让开发者用不好 /// Thread:框架没有去控制线程数量:容易滥用,就疯狂开启线程数量,其实在开发中,业务处理不好,开线程让服务器扛不住; /// 池化思想:如果某个对象创建和销毁代价比较高,同时这个对象还可以反复使用的,就需要一个池子,保存多个这样的对象,需要用的时候从池子 /// 里面获取;用完之后不用销毁,放回池子。(享元模式) /// 节约资源提升性能;此外,还能管控总数量,防止滥用。 /// /// </summary> private static void TreadPoolOperate() { Console.WriteLine("TreadPoolOperate主线程 start"); Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}"); //1如何分配一个线程; WaitCallback waitCallback = a => { Console.WriteLine($"waitCallback参数为{a}"); Console.WriteLine("线程池分配的线程ID为:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("TreadPoolOperating..."); }; //ThreadPool.QueueUserWorkItem(waitCallback);//无参数 ThreadPool.QueueUserWorkItem(waitCallback, "waitCallback paramInfo");
可以通过ThreadPool提供的API对线程池中线程数目进行相关操作,如下所示:
{ // 2设置线程池中线程数量 //workerThreads: // 线程池中辅助线程的最大数目。 // // completionPortThreads: // 线程池中异步 I/O 线程的最大数目 ThreadPool.SetMinThreads(8, 8); ThreadPool.SetMaxThreads(18, 18);//在之前最大线程不能低于计算机的线程数 //out int minworkTheead 新语法支持引用,相比之前简写了。 ThreadPool.GetMinThreads(out int minworkTheead, out int mincompletionPortThreads); Console.WriteLine($"min this workTheead= {minworkTheead},this completionPortThreads={mincompletionPortThreads}"); ThreadPool.GetMaxThreads(out int maxworkTheead, out int maxcompletionPortThreads); Console.WriteLine($"max this workTheead= {maxworkTheead},this completionPortThreads={maxcompletionPortThreads}"); //线程池里的线程可以设置,但是不要随便折腾,因为设置以后,线程相对于当前线程而言,是全局的 //Task parallel都是来自于线程池。但是new thread由可以新开一个线程,会占用一个线程池的位置 }
ManualResetEvent线程等待
ManualResetEvent被用于在两个或多个线程间进行线程信号发送。 多个线程可以通过调用ManualResetEvent对象的WaitOne方法进入等待或阻塞状态。当控制线程调用Set()方法,所有等待线程将恢复并继续执行。
ManualResetEvent对象的初始化
// // 摘要: // 通知一个或多个正在等待的线程已发生事件。 此类不能被继承。 [ComVisible(true)] public sealed class ManualResetEvent : EventWaitHandle { // // 摘要: // 用一个指示是否将初始状态设置为终止的布尔值初始化 System.Threading.ManualResetEvent 类的新实例。 // // 参数: // initialState: // 如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止。 public ManualResetEvent(bool initialState); }
其构造函数传入一个bool值,如果bool值为False,则使所有线程阻塞,反之,如果bool值为True,则使所有线程退出阻塞。其默认初始值为False
ManualResetEvent mre = new ManualResetEvent(false);
WaitOne方法的使用
该方法阻塞当前线程并等待其他线程发送信号。如果收到信号,它将返回True,反之返回False。以下演示了如何调用该方法。
该方法存在于abstract class WaitHandle类中,是一个虚方法。
API信息如下【其还有很多重载方法,此处不赘述】:
// // 摘要: // 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。 // // 返回结果: // 如果当前实例收到信号,则为 true。 如果当前实例永不发出信号,则 System.Threading.WaitHandle.WaitOne(System.Int32,System.Boolean) // 永不返回。 // // 异常: // T:System.ObjectDisposedException: // 已释放当前实例。 // // T:System.Threading.AbandonedMutexException: // 等待结束,因为线程在未释放互斥的情况下退出。 在 Windows 98 或 Windows Millennium Edition 上不会引发此异常。 // // T:System.InvalidOperationException: // 当前实例是另一个应用程序域中的 System.Threading.WaitHandle 的透明代理。 public virtual bool WaitOne();
Set方法
该方法用于给所有等待线程发送信号。 Set() 方法的调用使得ManualResetEvent对象的bool变量值为True,所有线程被释放并继续执行。
Reset方法
一旦我们调用了ManualResetEvent对象的Set()方法,它的bool值就变为true,我们可以调用Reset()方法来重置该值,Reset()方法重置该值为False。
此处初始化了一个值为False的ManualResetEvent对象,这意味着所有调用WaitOne的线程将被阻塞【此例主线程调用了WaitOne】,直到有线程调用了 Set() 方法【此处子线程语句最后将事件信号状态变为了True,主线程WaitOne就能继续执行,而不用等待了】。
private static void TreadPoolOperate() { Console.WriteLine("TreadPoolOperate主线程 start"); Console.WriteLine($"主线程ID为:{Thread.CurrentThread.ManagedThreadId}");
//3线程等待:观望式 //ManualResetEvent默认要求参数状态为false--mre.set();打开 //ManualResetEvent 参数状态为true->open-->mre.reset();关闭 ManualResetEvent mre = new ManualResetEvent(false);//如果为 true,则将初始状态设置为终止;如果为 false,则将初始状态设置为非终止 ThreadPool.QueueUserWorkItem(a => { Console.WriteLine(a); Console.WriteLine("当前子线程ID为" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine("线程池里面的线程执行完毕"); //将事件状态设置为有信号,从而允许一个或多个等待线程继续执行。 mre.Set();//信号由false变为true }); Console.WriteLine("do something else"); Console.WriteLine("do something else"); Console.WriteLine("do something else"); //// 阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。 mre.WaitOne();//判断信号,没有信号(为false),继续等起,有信号了(mre.Set()后,信号为true),不用等待了,可以往后执行了
//只要mre的状态变为true,则继续往后执行 Console.WriteLine("全部任务执行完成");
而如果我们用值True来对ManualResetEvent对象进行初始化,所有调用WaitOne方法的线程并不会被阻塞,可以进行后续的执行。
Thead、TheadPool扩展封装
那如果初始状态设置为无信号,后面又执行WaitOne无限等待,那就会出现死锁的状态。如下例所示:
#region myregion { ThreadPool.SetMaxThreads(32, 32);// ManualResetEvent mre1 = new ManualResetEvent(false); for (int i = 0; i < 32; i++) { int k = i; ThreadPool.QueueUserWorkItem(t => { Console.WriteLine($"thread ID={Thread.CurrentThread.ManagedThreadId}"); }); if (k == 31) { mre1.Set(); } else { mre1.WaitOne();//死锁了 Console.WriteLine("mre1.WaitOne()"); } } mre1.WaitOne(); Console.WriteLine("所有任务执行完成"); } #endregion