C#基礎系列——多線程的常見用法詳解


前言:前面幾節分別介紹了下C#基礎技術中的反射特性泛型序列化擴展方法Linq to Xml等,這篇跟着來介紹下C#的另一基礎技術的使用。最近項目有點緊張,所以准備也不是特別充分。此篇就主要從博主使用過的幾種多線程的用法從應用層面大概介紹下。文中觀點都是博主個人的理解,如果有不對的地方望大家指正~~

 

1、多線程:使用多個處理句柄同時對多個任務進行控制處理的一種技術。據博主的理解,多線程就是該應用的主線程任命其他多個線程去協助它完成需要的功能,並且主線程和協助線程是完全獨立進行的。不知道這樣說好不好理解,后面慢慢在使用中會有更加詳細的講解。

 

2、多線程的使用:

(1)最簡單、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});這種用法應該大多數人都使用過,參數為一個ThreadStart類型的委托。將ThreadStart轉到定義可知:

public delegate void ThreadStart();

它是一個沒有參數,沒有返回值的委托。所以他的使用如下:

static void Main(string[] args)
{
   Thread oGetArgThread = new Thread(new ThreadStart(Test));
    oGetArgThread.IsBackground = true;
    oGetArgThread.Start();   

    for (var i = 0; i < 1000000; i++)
    {
      Console.WriteLine("主線程計數" + i);
      //Thread.Sleep(100);
    }

}

private static void Test()
 {
       for (var i = 0; i < 1000000; i++)
       {
           Console.WriteLine("后台線程計數" + i);
           //Thread.Sleep(100);
       }
 }

定義一個沒有參數沒有返回值的方法傳入該委托。當然也可以不定義方法寫成匿名方法:

        static void Main(string[] args)
        {
            Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() =>
            {
                
                for (var i = 0; i < 1000000; i++)
                {
                    Console.WriteLine("后台線程計數" + i);
                    //Thread.Sleep(100);
                }
            }));
            oGetArgThread.IsBackground = true;
            oGetArgThread.Start();

這個和上面的意義相同。得到的結果如下:

說明主線程和后台線程是互相獨立的。由系統調度資源去執行。

如果這樣那有人就要問了,如果我需要多線程執行的方法有參數或者有返回值或者既有參數又有返回值呢。。。別着急我們來看看new Thread()的幾個構造函數:

public Thread(ParameterizedThreadStart start);
        public Thread(ThreadStart start);
        public Thread(ParameterizedThreadStart start, int maxStackSize);
        public Thread(ThreadStart start, int maxStackSize);

轉到定義可知參數有兩類,一類是無參無返回值的委托,另一類是有參無返回值的委托。對於有參數的委托使用方法:

    static void Main(string[] args)
        {
            Thread oThread = new Thread(new ParameterizedThreadStart(Test2));     
            oThread.IsBackground = true;
            oThread.Start(1000);
         }

     private static void Test2(object Count)
        {
            for (var i = 0; i < (int)Count; i++)
            {
                Console.WriteLine("后台線程計數" + i);
                //Thread.Sleep(100);
            }
        }    

對於有參又有返回值的委托,很顯然使用new Thread()這種方式是沒有解決方案的。其實對於有參又有返回值的委托可以使用異步來實現:

public delegate string MethodCaller(string name);//定義個代理 
MethodCaller mc = new MethodCaller(GetName); 
string name = "my name";//輸入參數 
IAsyncResult result = mc.BeginInvoke(name,null, null); 
string myname = mc.EndInvoke(result);//用於接收返回值 
 
public string GetName(string name)    // 函數
{
    return name;
}    

關於這種方式還有幾點值得一說的是:

①Thread oGetArgThread = new Thread(new ThreadStart(Test));

oGetArgThread.Join();//主線程阻塞,等待分支線程運行結束,這一步看功能需求進行選擇,主要為了多個進程達到同步的效果

②線程的優先級可以通過Thread對象的Priority屬性來設置,Priority屬性對應一個枚舉:

public enum ThreadPriority
    {
        // 摘要: 
        //     可以將 System.Threading.Thread 安排在具有任何其他優先級的線程之后。
        Lowest = 0,
        //
        // 摘要: 
        //     可以將 System.Threading.Thread 安排在具有 Normal 優先級的線程之后,在具有 Lowest 優先級的線程之前。
        BelowNormal = 1,
        //
        // 摘要: 
        //     可以將 System.Threading.Thread 安排在具有 AboveNormal 優先級的線程之后,在具有 BelowNormal 優先級的線程之前。
        //     默認情況下,線程具有 Normal 優先級。
        Normal = 2,
        //
        // 摘要: 
        //     可以將 System.Threading.Thread 安排在具有 Highest 優先級的線程之后,在具有 Normal 優先級的線程之前。
        AboveNormal = 3,
        //
        // 摘要: 
        //     可以將 System.Threading.Thread 安排在具有任何其他優先級的線程之前。
        Highest = 4,
    }

從0到4,優先級由低到高。

③關於多個線程同時使用一個對象或資源的情況,也就是線程的資源共享,為了避免數據紊亂,一般采用.Net悲觀鎖lock的方式處理。

     private static object oLock = new object();
        private static void Test2(object Count)
        {
            lock (oLock)
            {
                for (var i = 0; i < (int)Count; i++)
                {
                    Console.WriteLine("后台線程計數" + i);
                    //Thread.Sleep(100);
                }
            }
        }

 

(2)Task方式使用多線程:這種方式一般用在需要循環處理某項業務並且需要得到處理后的結果。使用代碼如下:

List<Task> lstTaskBD = new List<Task>();
foreach (var bd in lstBoards)
    {
         var bdTmp = bd;//這里必須要用一個臨時變量
         var oTask = Task.Factory.StartNew(() =>
         {
              var strCpBdCmd = "rm -Rf " + bdTmp.Path + "/*;cp -R " + CombineFTPPaths(FTP_EMULATION_BD_ROOT,

"bd_correct") + "/* " + bdTmp.Path + "/";
              oPlink.Run(bdTmp.EmulationServer.BigIP, bdTmp.EmulationServer.UserName, bdTmp.EmulationServer.Password,

strCpBdCmd);
              Thread.Sleep(500);
           });
           lstTaskBD.Add(oTask);
    }
Task.WaitAll(lstTaskBD.ToArray());//等待所有線程只都行完畢

使用這種方式的時候需要注意這一句 var bdTmp = bd;這里必須要用一個臨時變量,要不然多個bd對象容易串數據。如果有興趣可以調試看看。這種方法比較簡單,就不多說了。當然Task對象的用法肯定遠不止如此,還涉及到任務的調度等復雜的邏輯。博主對這些東西理解有限,就不講解了。

 

(3)線程池的用法:一般由於考慮到服務器的性能等問題,保證一個時間段內系統線程數量在一定的范圍,需要使用線程池的概念。大概用法如下:

  public class CSpiderCtrl
    {

      //將線程池對象作為一個全局變量
        static Semaphore semaphore;

        public static void Run()
        {
            //1. 創建 SuperLCBB客戶端對象
            var oClient = new ServiceReference_SuperLCBB.SOAServiceClient();

       //2.初始化的時候new最大的線程池個數255(這個數值根據實際情況來判斷,如果服務器上面的東西很少,則可以設置大點)
            semaphore = new Semaphore(250, 255);

            CLogService.Instance.Debug("又一輪定時采集...");

            _TestBedGo(oClient);

        }

 

    //執行多線程的方法

    private static void _TestBedGo(ServiceReference_SuperLCBB.SOAServiceClient oClient)
        {
            List<string> lstExceptPDUs = new List<string>(){
                "SUPERLABEXP"
            };
            var oTestBedRes = oClient.GetTestBedExceptSomePDU(lstExceptPDUs.ToArray(), true);
            if (CKVRes.ERRCODE_SUCCESS != oTestBedRes.ErrCode)
            {
                CLogService.Instance.Error("xxx");
                return;
            }

            var lstTestBed = oTestBedRes.ToDocumentsEx();

            System.Threading.Tasks.Parallel.ForEach(lstTestBed, (oTestBed) =>
            {

         //一次最多255個線程,超過255的必須等待線程池釋放一個線程出來才行
                semaphore.WaitOne();

                //CLogService.Instance.Info("開始采集測試床:" + oTestBed[TBLTestBed.PROP_NAME]);
                //Thread.Sleep(2000);

                var strTestBedName = oTestBed[TBLTestBed.PROP_NAME] as string;
                var strSuperDevIP = oTestBed[TBLTestBed.PROP_SUPERDEVIP] as string;
                var strTestBedGID = oTestBed[TBLTestBed.PROP_GID] as string;
                var strPdu = oTestBed[TBLTestBed.PROP_PDUGID] as string;
                Thread.Sleep(new Random().Next(1000, 5000));
                var oGetRootDevicesByTestBedGIDRes = oClient.GetRootDevicesByTestBedGID(strTestBedGID);
                CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "開始");
                Stopwatch sp = new Stopwatch();
                sp.Start();
                if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < 2)
                {
                    CLogService.Instance.Debug("shit -- 3實驗室中測試床Name:" + strTestBedName + "2完成異常0");

       //這里很重要的一點,每一次return 前一定要記得釋放線程,否則這個一直會占用資源
                    semaphore.Release();
                    return;
                }


                var strXML = oGetRootDevicesByTestBedGIDRes.Documents[0];
                var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[1];
                //var strExeName = "RateSpider";


                var oSuperDevClient = new SuperDevClient(CSuperDev.ENDPOINT, string.Format(CSuperDev.SuperDevURL, strSuperDevIP));
                try
                {
                    oSuperDevClient.IsOK();
                }
                catch (Exception)
                {
                    CLogService.Instance.Error("測試床Name:" + strTestBedName + "異常,插件沒起");
                    semaphore.Release();
                    return;
                }


                //2.3.1.請求SuperDev.Server(SuperDevIP),發送Run(XML和Exename)
                var oRunExeRes = new CKVRes();
                try
                {
                    oRunExeRes = oSuperDevClient.RunExeEx(strExeName, false, new string[] { strXML });
                }
                catch
                {
                    //CLogService.Instance.Debug("測試床Name:" + strTestBedName + "異常:" + ex.Message);
                }
                sp.Stop();
                CLogService.Instance.Debug(strPdu + "——測試床Name:" + strTestBedName + "完成時間" + sp.Elapsed);

          //每一個線程完畢后記得釋放資源
                semaphore.Release();
            });
        }

   }

需要注意:Semaphore對象的數量需要根據服務器的性能來設定;System.Threading.Tasks.Parallel.ForEach這種方式表示同時啟動lstTestBed.Length個線程去做一件事情,可以理解為

foreach(var oTestbed in lstTestBed)
{
        Thread oThread=new Thread(new ThreadStart({   ...}));          
}

 

(4) 多線程里面還有一個值得一說的SpinWait類,用於提供對基於自旋的等待的支持。也就是說支持重復執行一個委托,知道滿足條件就返回,我們來看它的用法:

        public static void SpinUntil(Func<bool> condition);
      
        public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout);
      
        public static bool SpinUntil(Func<bool> condition, TimeSpan timeout);

這個方法有三個構造函數,后兩個需要傳入一個時間,表示如果再規定的時間內還沒有返回則自動跳出,防止死循環。

            SpinWait.SpinUntil(() =>
                    {
                        bIsworking = m_oClient.isworking(new isworking()).result;
                        return bIsworking == false;
                    }, 600000);
                    //如果等了10分鍾還在跳纖則跳出
                    if (bIsworking)
                    {
                        oRes.ErrCode = "false交換機跳纖時間超過10分鍾,請檢查異常再操作";
                        return oRes;
                    }

 

博主使用過的多線程用法大概就這么三大類,當然這些其中還涉及很多細節性的東西,博主原來使用這些的時候經常出現各種莫名的問題,可能還是沒用好的原因,對這些東西理解還不夠深刻。如果大家也遇到類似的問題可以拿出來探討!!

多線程用法示例

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM