熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

詳細講解C#的多線程能力

2022-06-13   來源: .NET編程 

  線程是允許進行並行計算的一個抽象概念在另一個線程完成計算任務的同時一個線程可以對圖像進行更新二個線程可以同時處理同一個進程發出的二個網絡請求我們在這篇文章中將重點討論Java和C#在線程方面的不同之處並將一些Java中線程的常用模式轉換為C#    
   從概念上講線程提供了一種在一個軟件中並行執行代碼的方式━━每個線程都同時在一個共享的內存空間中執行指令(當然是在一個處理器上這是通過處於運行狀態的線程的交替執行完成的因此每個線程都可以訪問一個程序內的數據結構由於這種原因多線程編程的難度就可想而知了因為一個程序內有許多不同的線程需要安全地共享數據    
   線程的創建和運行    
   Java在javalangThread和javalangRunnable類中提供了大部分的線程功能創建一個線程非常簡單就是擴展Thread類並調用start()通過創建一個執行Runnable()的類並將該類作為參數傳遞給Thread()也可以定義一個線程仔細地閱讀下面這個簡單的Java程序其中有個線程同時在從數到並將結果打印出來

      public   class   ThreadingExample    
   extends   Object   {    
   public   static   void   main(   String   args[]   )   {    
   Thread[]   threads   =   new   Thread[];    
   for(   int   count=;count<=threadslength;count   )   {    
   threads[count]   =   new   Thread(   new   Runnable()   {    
   public   void   run()   {    
   count();    
       
   }    
       
   }   );    
   threads[count]start();    
       
   }    
       
   }    
       
   public   static   void   count()   {    
   for(   int   count=;count<=;count   )    
   Systemoutprint(   count       );    
       
   }    
       
   } 

  我們可以使用SystemThreadingThread和SystemThreadingThreadStart二個類將上述的Java程序轉換為C#語言

      using   SystemThreading;    
   public   class   ThreadingExample   :   Object   {    
   public   static   void   Main()   {    
   Thread[]   threads   =   new   Thread[];    
   for(   int   count=;count<=threadsLength;count   )   {    
   threads[count]   =   new   Thread(   new   ThreadStart(   Count   )   );    
   threads[count]Start();    
       
   }    
       
   }    
       
   public   static   void   Count()   {    
   for(   int   count=;count<=;count   )    
   ConsoleWrite(   count       );    
       
   }    
       
   } 

  這個例子中有一些小技巧Java允許擴展javalangThread類和執行javalangRunnable接口C#則沒有為我們提供這些便利一個C#中的Thread對象是不可知的必須通過ThreadStart進行創建這意味著不能使用內部的類模式而必須創建一個對象而且必須傳遞給線程一個對象的方法供線程執行用    

  線程的使用    
   Java中存在許多編程人員希望能夠對線程使用的標准操作例如測試線程是否存在加入一個線程直到它死亡殺死一個線程等    
   表線程管理的函數    
   Java中javalangThread中的方法和C#中SystemThreadingThread對象的對比    
       
   setDaemon(   boolean   on)   方法    
   IsBackground   設置屬性值    
   使一個存在的進程成為一個新線程(如果剩下的所有進程都成了新線程程序將停止運行)    
       
   isDaemon()方法    
   IsBackground   獲取屬性    
   如果該線程是一個後台線程則返回真值    
       
   isAlive()   方法    
   IsAlive   獲取屬性    
   如果該線程處於活動狀態則返回真值    
       
   interrupt()   方法    
   Interrupt()   方法    
   盡管在Java中這一方法可以用來設置線程的中斷狀態而且可以用來檢查線程是否被中斷在C#中沒有相應的方法對一個沒有處於阻塞狀態的線程執行Interrupt方法將使下一次阻塞調用自動失效    
       
   isInterrupted()   方法    
   n/a    
   如果該線程處於阻塞狀態則返回真值    
       
   sleep(   long   millis   )和sleep(   long   millis   int   nanos   )    
   Sleep(   int   millisecondTimeout   )   and   Sleep(   SystemTimeSpan   )方法    
   使正在執行的線程暫停一段給定的時間或直到它被中斷這一方法將在Java中將產生一個javalangInterruptedException狀態在C#中將產生SystemThreading   ThreadInterruptedException狀態    
       
   join()join(   long   millis   )和join(   long   millis   int   nanos   )   方法    
   Join()Join(   int   millisecondTimeout   )和Join(   SystemTimeSpan   )   方法   與Java中僅依靠超時設定不同的是在C#語言中則依據線程停止運行是由於線程死亡(返回真)或是超時(返回假)而返回一個布爾型變量    
       
   suspend()   方法    
   Suspend()   方法    
   二者的功能相同這一方法容易引起死循環如果一個占有系統關健資源的線程被掛起來則在這一線程恢復運行之前其他的線程不能訪問該資源    
       
   resume()   方法    
   Resume()   方法    
   恢復一個被掛起的線程    
       
   stop()   方法    
   Abort()   方法    
   參見下面的線程停止部分    
   (特別說明在上面的表中每個小節的第一行是java中的方法第二行是C#中的方法第三行是有關的注釋由於在文本文件中不能組織表格請編輯多費點心組織表格原文中有表格的格式)    
   線程的中止    
   由於能夠在沒有任何征兆的情況下使運行的程序進入一種混亂的狀態Java中的Threadstop受到了普遍的反對根據所調用的stop()方法一個未經檢查的javalangThreadDeath錯誤將會破壞正在運行著的程序的棧隨著它的不斷運行能夠解除任何被鎖定的對象由於這些鎖被不分青紅皂白地被打開由它們所保護的數據就非常可能陷入混亂狀態中    
   根據當前的Java文檔推薦的中止一個線程的方法是讓運行的線程檢查一個由其他的線程能夠改變的變量該變量代表一個死亡時間條件下面的程序就演示了這種方法

      //   條件變量    
   private   boolean   timeToDie   =   false;    
       
   //   在每次迭代中對條件變量進行檢查    
   class   StoppableRunnable    
   extends   Runnable   {    
   public   void   run()   {    
   while(   !timeToDie   )   {    
   //   進行相應的操作    
       
   }    
       
   }    
       
   }       
     
   上述的討論對C#中的Abort方法也適合根據調用的Abort方法令人捉摸不定的SystemThreadingThreadAbortException可能會破壞線程的棧它可能釋放線程保持的一些變量使處於保護狀態中的數據結構出現不可預測的錯誤我建議使用與上面所示的相似的方法來通知一個應該死亡的線程   

  線程的同步    
   從概念上來看線程非常易於理解實際上由於他們可能交互地對同一數據結構進行操作因此它們成為了令編程人員頭疼的一種東西以本文開始的ThreadingExample為例當它運行時會在控制台上輸出多種不同的結果從                     到                     或                     在內的各種情況都是可能出現的輸出結果可能與操作系統的線程調度方式之間的差別有關有時需要確保只有一個線程能夠訪問一個給定的數據結構以保證數據結構的穩定這也是我們需要線程同步機制的原因所在    
   為了保證數據結構的穩定我們必須通過使用來調整二個線程的操作順序二種語言都通過對引用的對象申請一個一旦一段程序獲得該的控制權後就可以保證只有它獲得了這個能夠對該對象進行操作同樣利用這種鎖一個線程可以一直處於等待狀態直到有能夠喚醒它信號通過變量傳來為止    
   表線程同步    
   需要對線程進行同步時需要掌握的關健字    
   synchronized    
   lock    
   C#中的lock命令實際上是為使用SystemThreadingMonitor類中的Enter和Exit方法的語法上的准備    
       
   Objectwait()    
   MonitorWait(   object   obj   )    
   C#中沒有等待對象的方法如果要等待一個信號則需要使用SystemThreadingMonitor類這二個方法都需要在同步的程序段內執行    
       
   Objectnotify()    
   MonitorPulse(   object   obj   )    
   參見上面的MonitorWait的注釋    
       
   Objectnotify()    
   MonitorPulseAll(   object   obj   )    
   參見上面的MonitorWait的注釋    
   (特別說明在上面的表中每個小節的第一行是java中的方法第二行是C#中的方法第三行是有關的注釋由於在文本文件中不能組織表格請編輯多費點心組織表格原文中有表格的格式)    
       
   我們可以對上面的例子進行一些適當的修改通過首先添加一個進行同步的變量然後對count()方法進行如下的修改使變量在中被執行加操作

      public   static   Object   synchronizeVariable   =   locking   variable;    
       
   public   static   void   count()   {    
   synchronized(   synchronizeVariable   )   {    
   for(   int   count=;count<=;count   )   {    
   Systemoutprint(   count       );    
   synchronizeVariablenotifyAll();    
   if(   count   <     )    
   try   {    
   synchronizeVariablewait();    
       
   }   catch(   InterruptedException   error   )   {    
       
   }    
       
   }    
       
   }    
       
   } 

  作了上述的改變後每次只有一個線程(因為一次只能有一個線程獲得synchronizeVariable)能夠執行for   loop循環輸出數字然後它會喚醒所有等待synchronizeVariable的線程(盡管現在還沒有線程處於等待狀態並試圖獲得被鎖著的變量然後等待再次獲得鎖變量下一個線程就可以開始執行for   loop循環輸出數字調用notifyAll()喚醒前面的線程並使它開始試圖獲得synchronizeVariable變量使自己處於等待狀態釋放synchronizeVariable允許前面的線程獲得它這個循環將一直進行下去直到它們都輸出完從的數字    
   通過一些簡單的語法變化可以將上述的修改在C#中實現

      public   static   Object   synchronizeVariable   =   locking   variable;    
       
   public   static   void   count()   {    
   lock(   synchronizeVariable   )   {    
   for(   int   count=;count<=;count   )   {    
   Systemoutprint(   count       );    
   MonitorPulseAll(   synchronizeVariable   );    
   if(   count   <     )    
   MonitorWait(   synchronizeVariable   );    
       
   }    
       
   }    
       
   } 

  C#中特有的線程功能    
   象我們一直對C#所抱的期望那樣C#中確實有一些Java不支持的方法類和函數對於鐵桿的Java線程編程人員而言這可是一件好事因為他們可以用C#編寫代碼然後在Java代碼中引用    
   Enter/TryEnter/Exit    
   要在Java中獲得某一變量的鎖必須在代碼的首尾二端加上synchronized關健字指明需要獲得鎖的對象一旦線程開始執行synchronized塊中的代碼它就獲得了對這一對象的鎖的控制權同樣一旦線程已經離開了synchronized塊它也將釋放這一對象的鎖我們已經知道C#也有一個相似的被稱作lock的關健字除了lock這個關健字外C#還提供了內置的獲得和釋放鎖的方法MonitorEnter(   object   obj   )和   MonitorExit(   object   obj   )通過使用這些方法編程人員可以獲得與使用lock相同的作用但提供了更精確的控制方法例如可以在一個方法中鎖定幾個變量而不同時或在代碼中的不同部分釋放它們    
   對一個需要進行同步的對象執行SystemThreadingMonitorEnter操作將使線程獲得該對象的鎖或者在由其他線程控制著該對象的鎖時進行阻塞通過執行MonitorExit方法就可以釋放鎖如果線程已經不控制著該對象的鎖了這一方法將會產生一個SystemThreadingSynchronizationLockException異常信號    
   C#中的Monitor類不但包括Enter方法還包括TryEnter方法如果執行該方法就會或者獲得一個鎖或者返回一個表明它不能獲得鎖的返回值    
   原子操作    
   SystemThreadingInterlocked類提供了程序對由幾個線程共享的變量進行同步訪問的能力C#把一些操作抽象為原子操作或不可分割的操作為了說明這一問題是如何解決的我們來看一下下面的Java代碼

      public   static   int   x   =   ;    
       
   public   static   void   increment()   {    
   x   =   x   ;    
       
   } 

  如果有二個不同的線程同時調用increment()x最後的值可能是發生這種情況的原因可能是二個進程無序地訪問x變量在沒有將x置初值時對它執行加操作在任一線程有機會對x執行加操作之前二個線程都可能將x讀作並將它設置為新的值    
   在Java和C#中我們都可以實現對x變量的同步訪問所有進程都可以按各自的方式運行但通過使用Interlocked類C#提供了一個對這一問題更徹底的解決方案Interlocked類有一些方法例如Increment(   ref   int   location   )Decrement(   ref   int   location   )這二個方法都取得整數型參數對該整數執行加或減操作並返回新的值所有這些操作都以不可分割的方式進行這樣就無需單獨創建一個可以進行同步操作的對象如下例所示

      public   static   Object   locker   =      
   public   static   int   x   =   ;    
       
   public   static   void   increment()   {    
   synchronized(   locker   )   {    
   x   =   x   ;    
       
   }    
       
   } 

  C#中的Interlocked類可以用下面的代碼完成相同的操作

      public   static   int   x   =   ;    
       
   public   static   void   Increment()   {    
   InterlockedIncrement(   ref   x   );    
       
   }       
   Interlocked中還包括一個名字為Exchange的方法可以不可分割地將一個變量的值設置為另一個變量的值    
   線程池    
   如果許多利用了線程的應用軟件都創建線程這些線程將會因等待某些條件(鍵盤或新的I/O輸入等)而在等待狀態中浪費大部分的時間C#提供的SystemThreadingThreadPool對象可以解決這一問題使用ThreadPool和事件驅動的編程機制程序可以注冊一個SystemThreadingWaitHandle對象(WaitHandle是C#編程中等待和通知機制的對象模型)和SystemThreadingWaitOrTimerCallback對象所有的線程無需自己等待WaitHandle的釋放ThreadPool將監控所有向它注冊的WaitHandle然後在WaitHandle被釋放後調用相應WaitOrTimerCallback對象的方法    
   結束語    
   在本篇文章中我們簡單地討論了C#提供的用於線程和並行操作的機制其中的大部分與Java相似━━C#提供了可以運行提供的方法的Thread對象同時提供了對代碼訪問進行同步的方法與在其他方面一樣C#在線程方面也提供了一些Java不支持的語法(在一定程度上揭示了同步操作的一些底層的內容Java編程人員可能會發現這一部分非常有用


From:http://tw.wingwit.com/Article/program/net/201311/13293.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.