亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

解析C#多線程編程中異步多線程的實(shí)現(xiàn)及線程池的使用

 更新時(shí)間:2016年03月06日 18:10:09   作者:懶得安分  
這篇文章主要介紹了C#多線程編程中異步多線程的實(shí)現(xiàn)及線程池的使用,同時(shí)對(duì)多線程的一般概念及C#中的線程同步并發(fā)編程作了講解,需要的朋友可以參考下

0、線程的本質(zhì)
線程不是一個(gè)計(jì)算機(jī)硬件的功能,而是操作系統(tǒng)提供的一種邏輯功能,線程本質(zhì)上是進(jìn)程中一段并發(fā)運(yùn)行的代碼,所以線程需要操作系統(tǒng)投入CPU資源來(lái)運(yùn)行和調(diào)度。

1、多線程:

使用多個(gè)處理句柄同時(shí)對(duì)多個(gè)任務(wù)進(jìn)行控制處理的一種技術(shù)。據(jù)博主的理解,多線程就是該應(yīng)用的主線程任命其他多個(gè)線程去協(xié)助它完成需要的功能,并且主線程和協(xié)助線程是完全獨(dú)立進(jìn)行的。不知道這樣說(shuō)好不好理解,后面慢慢在使用中會(huì)有更加詳細(xì)的講解。

2、多線程的使用:

(1)最簡(jiǎn)單、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});這種用法應(yīng)該大多數(shù)人都使用過(guò),參數(shù)為一個(gè)ThreadStart類型的委托。將ThreadStart轉(zhuǎn)到定義可知:

public delegate void ThreadStart();

它是一個(gè)沒(méi)有參數(shù),沒(méi)有返回值的委托。所以他的使用如下:

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("主線程計(jì)數(shù)" + i);
      //Thread.Sleep(100);
    }

}

private static void Test()
 {
    for (var i = 0; i < 1000000; i++)
    {
      Console.WriteLine("后臺(tái)線程計(jì)數(shù)" + i);
      //Thread.Sleep(100);
    }
 }

定義一個(gè)沒(méi)有參數(shù)沒(méi)有返回值的方法傳入該委托。當(dāng)然也可以不定義方法寫成匿名方法:

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

這個(gè)和上面的意義相同。得到的結(jié)果如下:

201636180800984.png (677×442)

說(shuō)明主線程和后臺(tái)線程是互相獨(dú)立的。由系統(tǒng)調(diào)度資源去執(zhí)行。

如果這樣那有人就要問(wèn)了,如果我需要多線程執(zhí)行的方法有參數(shù)或者有返回值或者既有參數(shù)又有返回值呢。。。別著急我們來(lái)看看new Thread()的幾個(gè)構(gòu)造函數(shù):

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

轉(zhuǎn)到定義可知參數(shù)有兩類,一類是無(wú)參無(wú)返回值的委托,另一類是有參無(wú)返回值的委托。對(duì)于有參數(shù)的委托使用方法:

    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("后臺(tái)線程計(jì)數(shù)" + i);
        //Thread.Sleep(100);
      }
    }  

 

對(duì)于有參又有返回值的委托,很顯然使用new Thread()這種方式是沒(méi)有解決方案的。其實(shí)對(duì)于有參又有返回值的委托可以使用異步來(lái)實(shí)現(xiàn):

public delegate string MethodCaller(string name);//定義個(gè)代理 
MethodCaller mc = new MethodCaller(GetName); 
string name = "my name";//輸入?yún)?shù) 
IAsyncResult result = mc.BeginInvoke(name,null, null); 
string myname = mc.EndInvoke(result);//用于接收返回值 
 
public string GetName(string name)  // 函數(shù)
{
  return name;
}  

關(guān)于這種方式還有幾點(diǎn)值得一說(shuō)的是:

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

oGetArgThread.Join();//主線程阻塞,等待分支線程運(yùn)行結(jié)束,這一步看功能需求進(jìn)行選擇,主要為了多個(gè)進(jìn)程達(dá)到同步的效果

②線程的優(yōu)先級(jí)可以通過(guò)Thread對(duì)象的Priority屬性來(lái)設(shè)置,Priority屬性對(duì)應(yīng)一個(gè)枚舉:

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

從0到4,優(yōu)先級(jí)由低到高。

③關(guān)于多個(gè)線程同時(shí)使用一個(gè)對(duì)象或資源的情況,也就是線程的資源共享,為了避免數(shù)據(jù)紊亂,一般采用.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("后臺(tái)線程計(jì)數(shù)" + i);
          //Thread.Sleep(100);
        }
      }
    }

 

(2)Task方式使用多線程:

這種方式一般用在需要循環(huán)處理某項(xiàng)業(yè)務(wù)并且需要得到處理后的結(jié)果。使用代碼如下:

List<Task> lstTaskBD = new List<Task>();
foreach (var bd in lstBoards)
  {
     var bdTmp = bd;//這里必須要用一個(gè)臨時(shí)變量
     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());//等待所有線程只都行完畢

使用這種方式的時(shí)候需要注意這一句 var bdTmp = bd;這里必須要用一個(gè)臨時(shí)變量,要不然多個(gè)bd對(duì)象容易串?dāng)?shù)據(jù)。如果有興趣可以調(diào)試看看。這種方法比較簡(jiǎn)單,就不多說(shuō)了。當(dāng)然Task對(duì)象的用法肯定遠(yuǎn)不止如此,還涉及到任務(wù)的調(diào)度等復(fù)雜的邏輯。博主對(duì)這些東西理解有限,就不講解了。

 (3)異步操作的本質(zhì)
  所有的程序最終都會(huì)由計(jì)算機(jī)硬件來(lái)執(zhí)行,所以為了更好的理解異步操作的本質(zhì),我們有必要了解一下它的硬件基礎(chǔ)。 熟悉電腦硬件的朋友肯定對(duì)DMA這個(gè)詞不陌生,硬盤、光驅(qū)的技術(shù)規(guī)格中都有明確DMA的模式指標(biāo),其實(shí)網(wǎng)卡、聲卡、顯卡也是有DMA功能的。DMA就是直 接內(nèi)存訪問(wèn)的意思,也就是說(shuō),擁有DMA功能的硬件在和內(nèi)存進(jìn)行數(shù)據(jù)交換的時(shí)候可以不消耗CPU資源。只要CPU在發(fā)起數(shù)據(jù)傳輸時(shí)發(fā)送一個(gè)指令,硬件就開(kāi) 始自己和內(nèi)存交換數(shù)據(jù),在傳輸完成之后硬件會(huì)觸發(fā)一個(gè)中斷來(lái)通知操作完成。這些無(wú)須消耗CPU時(shí)間的I/O操作正是異步操作的硬件基礎(chǔ)。所以即使在DOS 這樣的單進(jìn)程(而且無(wú)線程概念)系統(tǒng)中也同樣可以發(fā)起異步的DMA操作。

(4)異步操作的優(yōu)缺點(diǎn)
  因?yàn)楫惒讲僮鳠o(wú)須額外的線程負(fù)擔(dān),并且使用回調(diào)的方式進(jìn)行處理,在設(shè)計(jì)良好的情況下,處理函數(shù)可以不必使用共享變量(即使無(wú)法完全不用,最起碼可以減少 共享變量的數(shù)量),減少了死鎖的可能。當(dāng)然異步操作也并非完美無(wú)暇。編寫異步操作的復(fù)雜程度較高,程序主要使用回調(diào)方式進(jìn)行處理,與普通人的思維方式有些出入,而且難以調(diào)試。

3、線程池的用法:

一般由于考慮到服務(wù)器的性能等問(wèn)題,保證一個(gè)時(shí)間段內(nèi)系統(tǒng)線程數(shù)量在一定的范圍,需要使用線程池的概念。大概用法如下:

  public class CSpiderCtrl
  {

     //將線程池對(duì)象作為一個(gè)全局變量
    static Semaphore semaphore;

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

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

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

      _TestBedGo(oClient);

    }

 

   //執(zhí)行多線程的方法

   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個(gè)線程,超過(guò)255的必須等待線程池釋放一個(gè)線程出來(lái)才行
        semaphore.WaitOne();

        //CLogService.Instance.Info("開(kāi)始采集測(cè)試床:" + 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 + "——測(cè)試床Name:" + strTestBedName + "開(kāi)始");
        Stopwatch sp = new Stopwatch();
        sp.Start();
        if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < 2)
        {
          CLogService.Instance.Debug("shit -- 3實(shí)驗(yàn)室中測(cè)試床Name:" + strTestBedName + "2完成異常0");

       //這里很重要的一點(diǎn),每一次return 前一定要記得釋放線程,否則這個(gè)一直會(huì)占用資源
          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("測(cè)試床Name:" + strTestBedName + "異常,插件沒(méi)起");
          semaphore.Release();
          return;
        }


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

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

  }

需要注意:Semaphore對(duì)象的數(shù)量需要根據(jù)服務(wù)器的性能來(lái)設(shè)定;System.Threading.Tasks.Parallel.ForEach這種方式表示同時(shí)啟動(dòng)lstTestBed.Length個(gè)線程去做一件事情,可以理解為

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

 

(4) 多線程里面還有一個(gè)值得一說(shuō)的SpinWait類,用于提供對(duì)基于自旋的等待的支持。也就是說(shuō)支持重復(fù)執(zhí)行一個(gè)委托,知道滿足條件就返回,我們來(lái)看它的用法:

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

這個(gè)方法有三個(gè)構(gòu)造函數(shù),后兩個(gè)需要傳入一個(gè)時(shí)間,表示如果再規(guī)定的時(shí)間內(nèi)還沒(méi)有返回則自動(dòng)跳出,防止死循環(huán)。

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

4、多線程的優(yōu)缺點(diǎn)
多線程的優(yōu)點(diǎn)很明顯,線程中的處理程序依然是順序執(zhí)行,符合普通人的思維習(xí)慣,所以編程簡(jiǎn)單。但是多線程的缺點(diǎn)也同樣明顯,線程的使用(濫用)會(huì)給系統(tǒng)帶來(lái)上下文切換的額外負(fù)擔(dān)。并且線程間的共享變量可能造成死鎖的出現(xiàn)。

5、適用范圍
在了解了線程與異步操作各自的優(yōu)缺點(diǎn)之后,我們可以來(lái)探討一下線程和異步的合理用途。我認(rèn)為:當(dāng)需要執(zhí)行I/O操作時(shí),使用異步操作比使用線程+同步 I/O操作更合適。I/O操作不僅包括了直接的文件、網(wǎng)絡(luò)的讀寫,還包括數(shù)據(jù)庫(kù)操作、Web Service、HttpRequest以及.net Remoting等跨進(jìn)程的調(diào)用。

而線程的適用范圍則是那種需要長(zhǎng)時(shí)間CPU運(yùn)算的場(chǎng)合,例如耗時(shí)較長(zhǎng)的圖形處理和算法執(zhí)行。但是往往由于使用線程編程的簡(jiǎn)單和符合習(xí)慣,所以很多朋友往往會(huì)使用線程來(lái)執(zhí)行耗時(shí)較長(zhǎng)的I/O操作。這樣在只有少數(shù)幾個(gè)并發(fā)操作的時(shí)候還無(wú)傷大雅,如果需要處理大量的并發(fā)操作時(shí)就不合適了。

相關(guān)文章

最新評(píng)論