1. 程式人生 > 實用技巧 >C#中的多執行緒Thread

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}"); }

執行緒執行的方法帶引數

如果該方法具有引數,則將委託傳遞

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);

    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