1. 程式人生 > >C#基礎系列——多執行緒的常見用法詳解

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

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

相關推薦

C#基礎系列——執行常見用法

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

c#基礎知識---執行

執行緒 被定義為程式的執行路徑。每個執行緒都定義了一個獨特的控制流。如果您的應用程式涉及到複雜的和耗時的操作,那麼設定不同的執行緒執行路徑往往是有益的,每個執行緒執行特定的工作。 執行緒是輕量級程序。一個使用執行緒的常見例項是現代作業系統中並行程式設計的實現。使用執行緒節省了 CPU 週期的浪費,同時

C++11執行(七):《 三:std::future & std::shared_future》

#include <iostream>                // std::cout #include <future>                // std::async, std::future #include <chrono>            

C++執行函式CreateThread()

採用CreateThread()建立多執行緒程式 原創 2012年12月10日 11:44:59

Java執行——synchronized使用

一 前言 Java多執行緒是面試必考的知識點,哈哈,說的有點太絕對了,題外話不說那麼多了,今天我們就來了解一下Java多執行緒中的synchronized。 synchronized相信大家都看過或者用過,synchronized是Java中的關鍵字,synchronized可以

ThreadLocal 執行環境使用

ThreadLocal 介紹 ThreadLocal  就是一個Map。key - > Thread.getCurrentThread()。value - > 執行緒需要儲存的變數。在多執行緒環境中,相當於各自執行緒的一個內部map變數。 每個

執行屬性pthread_attr

Posix執行緒中的執行緒屬性pthread_attr_t主要包括scope屬性、detach屬性、堆疊地址、堆疊大小、優先順序。在pthread_create中,把第二個引數設定為NULL的話,將採用預設的屬性配置。 執行緒屬性結構如下: typedef s

Java執行---阻塞佇列(舉例說明)

一. 前言   在新增的Concurrent包中,BlockingQueue很好的解決了多執行緒中,如何高效安全“傳輸”資料的問題。通過這些高效並且執行緒安全的佇列類,為我們快速搭建高質量的多執行緒程式帶來極大的便利。本文詳細介紹了BlockingQueue家庭中的所有成員

java執行小示例

package com.chillax.controller; import java.io.IOException; public class MultiThread { public static void main(String[] args) { System.out.p

執行傳參

1.傳遞臨時物件做執行緒引數 1.1要避免的陷阱1 用detach()時,如果主執行緒先結束,變數就會被回收;所以用detach()的話,不推薦用引用,同時絕對不能用指標。 1.2要避免的陷阱2 只要臨時物件的用臨時構造A類物件作為引數傳遞給執行緒,那麼就一定能夠在主執行緒結束之

執行安全問題

在上一篇部落格中已經提到了什麼是多執行緒:https://blog.csdn.net/weixin_42647847/article/details/80969240那多執行緒在java中是如何實現的呢。一、實現多執行緒的四種方式1.繼承Thread類,重寫run方法2.實現

JAVA執行 join() 方法及應用場景

在某些情況下,主執行緒建立並啟動了子執行緒,如果子執行緒中需要進行大量的耗時運算,主執行緒往往將早於子執行緒結束之前結束,如果主執行緒想等待子執行緒執行完畢後,獲得子執行緒中的處理完的某個資料,就要用

NET執行同步方法(五):訊號量(Semaphore)

   訊號量就像一個夜總會:它有確切的容量,並被保鏢控制。一旦滿員,就沒有人能再進入,其他人必須在外面排隊。那麼在裡面離開一個人後,隊頭的人就可以進入。訊號量的建構函式需要提供至少兩個引數-現有的人數和最大的人數。 訊號

執行】BlockingQueue

前言:      在新增的Concurrent包中,BlockingQueue很好的解決了多執行緒中,如何高效安全“傳輸”資料的問題。通過這些高效並且執行緒安全的佇列類,為我們快速搭建高質量的多執行緒程式帶來極大的便利。本文詳細介紹了BlockingQueue家庭中的所有成員

PHP Curl執行原理例項

<?php  // 建立一對cURL資源  $ch1 = curl_init();  $ch2 = curl_init();  // 設定URL和相應的選項  curl_setopt($ch1, CURLOPT_URL, "http://www.jb51.net/");  curl_setopt($

Android 開發之執行處理——Handler

    Android開發過程中為什麼要多執行緒     我們建立的Service、Activity以及Broadcast均是一個主執行緒處理,這裡我們可以理解為UI執行緒。但是在操作一 些耗時操作時,比如I/O讀寫的大檔案讀寫,資料庫操作以及網路下載需要很長時間,為了不

Java執行-Thread.yield

http://blog.csdn.net/dabing69221/article/details/17426953 Thread.yield( )方法: 使當前執行緒從執行狀態(執行狀態)變為可執行態(就緒狀態)。cpu會從眾多的可執行態裡選擇,也就是說,當前也就

java執行-join方法(附面試題)

本文對java Thread中join()方法進行介紹,join()的作用是讓“主執行緒”等待“子執行緒”結束之後才能繼續執行,大家參考使用吧 本章涉及到的內容包括: 1. join()

理解 JAVA執行技術之

1.    虛假的多執行緒     例1:     public class TestThread     {     int i=0, j=0;     public void go(int flag){     while(true){     try{    

40道阿里巴巴JAVA研發崗執行面試題,你能答出多少

1、多執行緒有什麼用? 一個可能在很多人看來很扯淡的一個問題:我會用多執行緒就好了,還管它有什麼用?在我看來,這個回答更扯淡。