線程用Thread類來創建, 通過ThreadStart委托來指明方法從哪里開始運行。ThreadStart的聲明如下:
public delegate void ThreadStart();
調用Start方法后,線程開始運行,直到它所調用的方法返回后結束。
class ThreadTest { static void Main() { Thread t = new Thread (new ThreadStart (Go)); t.Start(); Go(); } static void Go() { Console.WriteLine ("hello!"); }一個線程可以通過C#的委托簡短的語法更便利地創建出來:
static void Main() { Thread t = new Thread (Go); // 不需要顯式聲明使用 ThreadStart t.Start(); ...}static void Go() { ... }在這種情況,ThreadStart被編譯器自動推斷出來:另一個快捷的方式是使用匿名方法來啟動線程
static void Main() { Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); }); t.Start();}線程有一個IsAlive屬性,在調用Start()之后直到線程結束之前一直為true。一個線程一旦結束便不能重新開始了。
將數據傳入ThreadStart中
假如想更好地區分開每個線程的輸出結果,如讓其中一個線程輸出大寫字母。可以考慮傳入一個狀態字到Go中來完成整個任務,此時就不能使用ThreadStart委托,因為它不接受參數。不過NET framework定義了另一個版本的委托叫ParameterizedThreadStart, 它可以接收一個單獨的object類型參數,委托聲明如下:
public delegate void ParameterizedThreadStart (object obj);
示例如下:
class ThreadDemo { static void Main() { Thread t = new Thread (Go); //編譯器自動推斷 t.Start (true); // == Go (true) Go (false); } static void Go (object upperCase) { bool upper = (bool) upperCase; Console.WriteLine (upper ? "HELLO!" : "hello!"); }在整個例子中,編譯器自動推斷出ParameterizedThreadStart委托,因為Go方法接收一個單獨的object參數,就像這樣寫:
Thread t = new Thread (new ParameterizedThreadStart (Go));t.Start (true);
ParameterizedThreadStart的特性是在使用之前我們必需對我們想要的類型(這里是bool)進行裝箱操作,并且它只能接收一個參數。
一個替代方案是使用一個匿名方法調用一個普通的方法如下:
static void Main() { Thread t = new Thread (delegate() { WriteText ("Hello"); }); t.Start();}static void WriteText (string text) { Console.WriteLine (text); }優點是目標方法(這里是WriteText)可以接收任意數量的參數,并且沒有裝箱操作。不過這需要將一個外部變量放入到匿名方法中,向下面的一樣:
static void Main() { string text = "Before"; Thread t = new Thread (delegate() { WriteText (text); }); text = "After"; t.Start();}static void WriteText (string text) { Console.WriteLine (text); }匿名方法出現了一種怪異的現象:當外部變量被后面的代碼修改了值的時候,線程可能會通過外部變量進行無意的互動。換個角度看,有意的互動(通常通過字段)也可以采用這種方式!一旦線程開始運行了,外部變量最好被處理成只讀的——除非有人愿意使用適當的鎖。
另一種較常見的方式是將對象實例的方法而不是靜態方法傳入到線程中,對象實例的屬性可以告訴線程要做什么,如下重寫了上節的例子:
class ThreadDemo{ bool upper; static void Main() { ThreadDemo instance1 = new ThreadDemo(); instance1.upper = true; Thread t = new Thread (instance1.Go); t.Start(); ThreadDemo instance2 = new ThreadDemo(); instance2.Go(); // 主線程——運行 upper=false } void Go() { Console.WriteLine (upper ? "HELLO!" : "hello!"); }命名線程
線程可以通過它的Name屬性進行命名,這非常有利于調試:可以用Console.WriteLine打印出線程的名字,Microsoft Visual Studio可以將線程的名字顯示在調試工具欄的位置上。線程的名字可以在被任何時間設置——但只能設置一次,重命名會引發異常。
程序的主線程也可以被命名,下面例子里主線程通過CurrentThread命名:
class ThreadNaming { static void Main() { Thread.CurrentThread.Name = "main"; Thread worker = new Thread (Go); worker.Name = "worker"; worker.Start(); Go(); } static void Go() { Console.WriteLine ("Hello from " + Thread.CurrentThread.Name); }}前臺和后臺線程
線程默認為前臺線程,這意味著任何前臺線程在運行都會保持程序存活。C#也支持后臺線程,當所有前臺線程結束后,它們不維持程序的存活。
改變線程從前臺到后臺不會以任何方式改變它在CPU協調程序中的優先級和狀態。
線程的IsBackground屬性控制它的前后臺狀態,如下實例:
class PRiorityTest { static void Main (string[] args) { Thread worker = new Thread (delegate() { Console.ReadLine(); }); if (args.Length > 0) worker.IsBackground = true; worker.Start(); }}
如果程序被調用的時候沒有任何參數,工作線程為前臺線程,并且將等待ReadLine語句來等待用戶的觸發回車,這期間,主線程退出,但是程序保持運行,因為一個前臺線程仍然活著。另一方面如果有參數傳入Main(),工作線程被賦值為后臺線程,當主線程結束程序立刻退出,終止了ReadLine。后臺線程這種終止方式,使任何最后操作都被規避了,這是不太合適的。好的方式是明確等待任何后臺工作線程完成后再結束程序,可能用一個timeout(大多用Thread.Join)。如果因為某種原因某個工作線程無法完成,可以試圖終止它,如果失敗了,再拋棄線程,允許它與進程一起消亡。(記錄是一個難題,但在這個場景下是有意義的)
擁有一個后臺工作線程是有益的,最直接的理由是結束程序時它可能有最后的發言權,與不會消亡的前臺線程一起保證程序的正常退出。拋棄一個前臺工作線程風險更大,尤其對Windows Forms程序,因為程序直到主線程結束時才退出(至少對用戶來說),但是它的進程仍然運行著。它將從應用程序欄消失不見,但卻可以在在Windows任務管理器進程欄找到它。除非手動找到并結束它,否則將繼續消耗資源,并可能阻止一個新的實例的重新開始運行或影響它的特性。
對于程序失敗退出的普遍原因就是存在“被忘記”的前臺線程。
線程優先級
線程的Priority 屬性確定了線程相對于其它同一進程的活動的線程擁有多少執行時間,以下是級別:
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }只有多個線程同時為活動時,優先級才有作用。
設置一個線程的優先級為高一些,并不意味著它能執行實時的工作,因為它受限于程序的進程級別。要執行實時的工作,必須提升在System.Diagnostics 命名空間下Process的級別,像下面這樣:
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
ProcessPriorityClass.High 其實是一個時間片中的最高優先級別:Realtime。設置進程級別到Realtime通知操作系統:你不想讓你的進程被搶占了。如果你的程序進入一個偶然的死循環,可以預期,操作系統被鎖住了,除了關機沒有什么可以拯救你了!基于此,High大體上被認為最高的有用進程級別。
如果一個實時的程序有一個用戶界面,提升進程的級別是不太好的,因為當用戶界面UI過于復雜的時候,界面的更新耗費過多的CPU時間,拖慢了整臺電腦。 降低主線程的級別、提升進程的級別、確保實時線程不進行界面刷新,但這樣并不能避免電腦越來越慢,因為操作系統仍會撥出過多的CPU給整個進程。最理想的方案是使實時工作和用戶界面在不同的進程(擁有不同的優先級)運行,通過Remoting或共享內存方式進行通信,共享內存需要Win32 API中的 P/Invoking。(可以搜索看看CreateFileMapping和MapViewOfFile)
異常處理
任何線程創建范圍內try/catch/finally塊,當線程開始執行便不再與其有任何關系??紤]下面的程序:
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // 不會在這得到異常 Console.WriteLine ("Exception!"); } static void Go() { throw null; }} 這里try/Word" style="background: none !
新聞熱點
疑難解答