程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 解析C#多線程編程中異步多線程的完成及線程池的應用

解析C#多線程編程中異步多線程的完成及線程池的應用

編輯:C#入門知識

解析C#多線程編程中異步多線程的完成及線程池的應用。本站提示廣大學習愛好者:(解析C#多線程編程中異步多線程的完成及線程池的應用)文章只能為提供參考,不一定能成為您想要的結果。以下是解析C#多線程編程中異步多線程的完成及線程池的應用正文


0、線程的實質
線程不是一個盤算機硬件的功效,而是操作體系供給的一種邏輯功效,線程實質上是過程中一段並發運轉的代碼,所以線程須要操作體系投入CPU資本來運轉和調劑。

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

這個和下面的意義雷同。獲得的成果以下:

201636180800984.png (677×442)

解釋主線程和後台線程是相互自力的。由體系調劑資本去履行。

假如如許那有人就要問了,假如我須要多線程履行的辦法有參數或許有前往值或許既有參數又有前往值呢。。。別焦急我們來看看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)異步操作的實質
  一切的法式終究都邑由盤算機硬件來履行,所認為了更好的懂得異步操作的實質,我們有需要懂得一下它的硬件基本。 熟習電腦硬件的同伙確定對DMA這個詞不生疏,硬盤、光驅的技巧規格中都有明白DMA的形式目標,其實網卡、聲卡、顯卡也是有DMA功效的。DMA就是直 接內存拜訪的意思,也就是說,具有DMA功效的硬件在和內存停止數據交流的時刻可以不用耗CPU資本。只需CPU在提議數據傳輸時發送一個指令,硬件就開 始本身和內存交流數據,在傳輸完成以後硬件會觸發一個中止來告訴操作完成。這些不必消費CPU時光的I/O操作恰是異步操作的硬件基本。所以即便在DOS 如許的單過程(並且無線程概念)體系中也異樣可以提議異步的DMA操作。

(4)異步操作的優缺陷
  由於異步操作不必額定的線程累贅,而且應用回調的方法停止處置,在設計優越的情形下,處置函數可以不用應用同享變量(即便沒法完整不消,最最少可以削減 同享變量的數目),削減了逝世鎖的能夠。固然異步操作也並不是完善得空。編寫異步操作的龐雜水平較高,法式重要應用回調方法停止處置,與通俗人的思想方法有些收支,並且難以調試。

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

4、多線程的優缺陷
多線程的長處很顯著,線程中的處置法式仍然是次序履行,相符通俗人的思想習氣,所以編程簡略。然則多線程的缺陷也異樣顯著,線程的應用(濫用)會給體系帶來高低文切換的額定累贅。而且線程間的同享變量能夠形成逝世鎖的湧現。

5、實用規模
在懂得了線程與異步操作各自的優缺陷以後,我們可以來商量一下線程和異步的公道用處。我以為:當須要履行I/O操作時,應用異步操作比應用線程+同步 I/O操作更適合。I/O操作不只包含了直接的文件、收集的讀寫,還包含數據庫操作、Web Service、HttpRequest和.net Remoting等跨過程的挪用。

而線程的實用規模則是那種須要長時光CPU運算的場所,例如耗時較長的圖形處置和算法履行。然則常常因為應用線程編程的簡略和相符習氣,所以許多同伙常常會應用線程來履行耗時較長的I/O操作。如許在只要多數幾個並發操作的時刻還無傷年夜雅,假如須要處置年夜量的並發操作時就不適合了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved