C#中的多執行緒Thread
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}"); }
執行緒執行的方法帶引數
如果該方法具有引數,則將委託傳遞
它具有簽名:
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);
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.View Code
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();View Code
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