本篇體驗:如何傳遞數據給線程,如何給線程命名,線程的異常處理,線程池。實在是太基礎的部分。
□ 傳遞數據給線程
※ 使用Lambda表達式
class Program{static void Main(string[] args){Thread t = new Thread(() => Say("hello", "world"));t.Start();}static void Say(string msg, string msg1){Console.WriteLine("第一個參數值是:"+msg);Console.WriteLine("第二個參數值是:" + msg1);}}
使用Lambda表達式需要注意的一個問題。
class Program{static void Main(string[] args){for (int i = 0; i < 10; i++){new Thread(() => Console.WriteLine("當前線程#" + Thread.CurrentThread.ManagedThreadId + "輸出的值是:" + i)).Start();}}}
說明,有些線程共享了局部變量i。如何解決線程共享棧變量的問題呢?只需要在每次遍歷循環的時候,把i賦值給一個臨時變量,再打印臨時變量,不直接打印棧變量i。
class Program{static void Main(string[] args){for (int i = 0; i < 10; i++){int temp = i;new Thread(() => Console.WriteLine("當前線程#" + Thread.CurrentThread.ManagedThreadId + "輸出的值是:" + temp)).Start();}}}
打印的值沒有重復的。
※ 使用線程的實例方法Start
class Program{static void Main(string[] args){Thread t = new Thread(Say);t.Start("hello");}static void Say(object msg){string str = (string) msg;Console.WriteLine(str);}}
之所以可以這樣做,是因為Thread的構造函數可以接收2種類型的形參。
public delegate void ThreadStart();public delegate void ParameterizedThreadStart (object obj);
所有符合ParameterizedThreadStart這個委托定義的方法,必須有一個object類型的參數,然後在實例方法Start中傳遞參數。使用這種方式傳遞參數的弊端是:
1、只能傳遞一個參數
2、在方法體中,每次都要對object類型進行轉換,拆箱
□ 給線程命名
class Program{static void Main(string[] args){Thread.CurrentThread.Name = "主線程";Thread t = new Thread(Say);t.Name = "其它工作線程";t.Start();Say();}static void Say(){Console.WriteLine("我的名字是:" + Thread.CurrentThread.Name);}}
通過Name屬性可以設置或獲取當前線程和實例線程的名稱。
□ 線程的異常處理
一個線程無法捕獲另外線程的異常。
class Program{static void Main(string[] args){try{new Thread(Say).Start();}catch (Exception){Console.WriteLine("有異常");}}static void Say(){throw null;}}
以上,在主線程的catch語句塊中,無法捕獲另外一個線程的異常。
實際上,一個線程的異常必須在該線程方法內捕獲。
class Program{static void Main(string[] args){new Thread(Say).Start();}static void Say(){try{throw null;}catch (Exception){Console.WriteLine("捕獲到異常了");}}}
□ 線程池
當創建一個線程,會用幾十到幾百毫秒的時間創建該線程的棧,而且,在默認情況下,每個線程需要1M的棧空間。線程池做到了共享和重復使用線程,性能得以提高。線程池還可以設置允許的最多線程數,一旦達到這個極限值,多余的線程需要排隊,等待線程池中的線程執行結束再進入。
線程池的默認的最大線程數因計算機不同而異。在.NET 4.0及其以後的版本中,在2位計算機最大線程數為1023個,64位為32768;在.NET3.5中,線程池最大線程數是250個;在.NET2.0中,線程池最大線程數為25個。當然,可以通過TreadPool.SetMaxThreads來設置線程池的最大線程數,也可以通過ThreadPool.SetMinThreads來設置最小線程數。
線程池中線程的IsBackground屬性默認為true。這意味著:主線程結束,應用程序結束,所有線程池中的後台線程結束。
※ 通過TPL進入線程池
TPL,Task Parallel Library,是一個處理並行任務的一個類庫。
class Program{static void Main(string[] args){Task tast = Task.Factory.StartNew(Say);tast.Wait();}static void Say(){Console.WriteLine("hello,我已經是線程池中的線程了");}}
使用System.Net.WebClient的實例方法DownloadString方法下載某個網頁,使用泛型Task<T>來處理。
class Program{static void Main(string[] args){Task<string> task = Task.Factory.StartNew<string>(() => DownloadString("http://www.baidu.com"));Console.WriteLine("Task在工作呢,我玩會啊~~");Console.WriteLine("Task在工作呢,我玩會啊~~");Console.WriteLine("Task在工作呢,我玩會啊~~");string result = task.Result;Console.WriteLine(result);}static string DownloadString(string uri){using (var wc = new System.Net.WebClient()){return wc.DownloadString(uri);}}}
※ 不通過TPL進入線程池
class Program{static void Main(string[] args){ThreadPool.QueueUserWorkItem(Say);ThreadPool.QueueUserWorkItem(Say, 123);Console.ReadLine();}static void Say(object data){Console.WriteLine("我來自線程池,我獲取到的數據是:" + data);}}
※ 使用委托進入線程池
class Program{static void Main(string[] args){Func<string, int> method = GetLength;IAsyncResult ia = method.BeginInvoke("test", null, null);Console.WriteLine("線程池的線程在工作者呢,我先玩會~");int result = method.EndInvoke(ia);Console.WriteLine("test的長度為:" + result);}static int GetLength(string s){return s.Length;}}
把方法賦值給委托變量,CLR會創建一個線程池中的線程。
線程系列包括:
參考資料:http://www.albahari.com/threading
LZ是自己繼承並實現execute方法嗎?還是使用Java提供的這個抽象類的一個實現類ThreadPoolExecutor?如果是用ThreadPoolExecutor,可以去看下ThreadFactory這接口
在你的表述中,“線程池不適合用來做耗時的任務”是最大誤區
1)你一定看到過System.Net.Socket類中有很多BeginXXX / EndXXX的方法,例如Socket.BeginReceiveFrom和Socket.EndReceiveFrom,這些函數統稱為異步函數。而異步函數操作的基礎恰恰就是線程池。對Socket通信而言,微軟提供的異步操作正是利用線程池中I/O線程,目的就是為了提高Socket I/O性能並簡化內存管理的!
2)如果對異步操作感到頭暈,在處理Socket操作時可以使用“顯式線程”方法,即按以下方法啟動處理線程:
System.Thread t = new System.Thread(你的處理函數);
t.IsBackground = true; //這個設置尤其重要!!!!!
t.Start();
一定要注意將線程設置為後台線程!
3)你一定知道系統每次啟動線程和銷毀線程時都會導致很大的開銷。當你的程序頻繁的啟動、銷毀線程,必然會導致程序很“卡”;正是由於這個緣故,微軟才搞了一個“線程池”。因為線程中的線程都是“啟動完畢的”(這樣表述雖不確切,但沒有錯),一旦你將異步處理函數“掛接”的線程池中的空閒線程上即可以執行你要的操作。而且,額外的好處是你根本不用去管理線程池中的線程(真正的“零”管理)
4)處理“耗時的操作”特別是涉及諸如Socket I/O 耗時操作,最佳的處理方法是利用後台線程(如果需要,同時配合以自定義事件event),這是增加用戶體驗不二法門哦~~