關于Thread的那些事
1 : 你可以調用線程的執行個體方法Join來等待一個線程的結束.例如:
public static void MainThread()
{
Thread t = new Thread(Go);
t.Start();
t.Join();
Console.WriteLine("Thread t has ended!");
}
static void Go()
{
for (int i = 0; i < 1000; i++)
Console.Write("y");
}
分析:在列印了1000個Y之後,後面就會輸出”Thread has ended!”.
你可以在調用Join方法的時候給它一個timeout的參數,例如要超過1秒.
t.Join(1000);
t.Join(TimeSpan.FromSeconds(1));
2 : 為線程傳遞參數
為線程傳遞參數的最簡單的方法莫過于執行一個lambda表達式,然後在方法裡面給參數了,例如:
static void Main(string[] args)
{
Thread t = new Thread(()=>Print("hello ,world !"));
t.Start();
Console.ReadKey();
}
static void Print(string message)
{
Console.WriteLine(message);
}
使用這種方法,你可以傳遞任何參數.
當然Thread的構造函數中有一個傳遞參數的版本,你也可以使用下面的代碼來傳遞參數:
static void Main()
{
Thread t = new Thread(Print);
t.Start("Hello from t!");
}
static void Print(object messageObj)
{
string message = (string)messageObj;
Console.WriteLine(message);
}
分析:這裡有一點需要注意,因為Print的方法簽名必須比對ParameterrizedThreadStart委托,是以Print的參數必須也是object,是以在Print方法中必須進行強制轉換.
3: Lambda和捕獲的變量
考慮下面的代碼
for (int i = 0; i < 10; i++)
{
new Thread(() => Console.Write(i)).Start();
}
分析:實際的輸出是不确定的,你可以自己試試.
Why?
關鍵問題是局部變量i在for循環中指向的是相同的記憶體位址.是以,每一次都在一個運作時會被改變值的變量(i)上調用CW方法,在foreach中也存在同樣問題.
解決這個問題的方法很簡單,例如使用一個臨時變量:
for (int i = 0; i < 10; i++)
{
int temp = i;
new Thread(() => Console.Write(temp)).Start();
}
因為i是值變量,是以int temp=i會複制i的值給temp,而在for循環中temp變量都是不同的,是以可以解決這個問題.
下面的也是同樣的道理:
for (int i = 0; i < 10; i++)
{
new Thread((obj) => Console.Write(obj)).Start(i); //因為每一個線程的obj都是不同的。
}
下面的案例可能更加明顯一點:
string text = "t1";
Thread t1 = new Thread(() => Console.WriteLine(text));
text = "t2";
Thread t2 = new Thread(() => Console.WriteLine(text));
t1.Start();
t2.Start();
不管你執行幾次,貌似都是輸出兩次t2,為啥呢?
因為兩個lambda表達式捕獲的是相同的test變量,是以”t2”會被連續列印兩次.
4.命名線程
給每一個線程一個合适的名字對于調試來說很有利,尤其是在VS中,因為縣城視窗和調試位置工具欄中都會顯示線程的名字.
但是你隻能設定一次線程的名字,嘗試在以後更改名字會抛出一個異常,為變量命名的使用的是Name屬性,例如:
Thread worker = new Thread(Go);
worker.Name = "worker";
worker.Name = "worker";//會抛出異常
5.前台線程和背景線程
預設你顯示建立的線程都是前台線程.
隻要前台線程有一個還在運作,應用程式就不會結束.
隻有所有的前台線程都結束了,應用程式才會結束.
在應用程式結束的時候,所有背景線程都會被終止.
你可以通過現成的IsBackground屬性萊産訊和更改現成的狀态.
案例:
if (args.Length>0)
{
worker.IsBackground = true;
}
worker.Start();
如果args.Length>0,則worker就是背景線程,那麼應用程式會立即終止.
否則worker預設就是前台線程,是以隻有在CR()方法結束後,應用程式才會終止.
是以當你有時候發現關閉了應用程式視窗,但是在任務管理器中應用程式仍然在運作,很有可能是還有一些前台線程在運作.
6.線程的優先級
enum ThreadPriority
{
Lowest,
BelowNormal,
Normal,
AboveNormal,
Highest
}
隻有在多個線程的環境中,線程優先級才有用.
把一個現成的優先級提高并不會提高實時系統的性能,因為程序的優先級才是應用程式的瓶頸.為了更好的實時工作,你必須提高程序的優先級.
例如:
process.PriorityClass=ProcessPriorityClass.High.
7.異常捕獲
嘗試在一個線程中捕獲另一個線程的異常是失敗的.案例:
public static void Main()
{
try
{
new Thread(Go).Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message); //永遠運作不到這裡.
}
}
static void Go() { throw null;}
我們再另一個線程中抛出了異常(throw null),然後嘗試在主線程中捕獲它,我們永遠都不會捕獲到這個異常.
為了在另一個線程中捕獲異常,必須在那個線程上try,catch,finally.
是以我們可以這麼做:
public static void Main()
{
new Thread(Go).Start();
}
static void Go()
{
try
{
throw null;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
8.全局捕獲異常
Application.DispatcherUnhandledException 事件和Application.ThreadException 事件都隻有在主UI線程中抛出異常的時候才會被觸發。
為了捕獲所有的未處理的異常,你可以使用AppDomain.CurrentDomain.UnhandledException,雖然這個事件在任何未處理異常抛出的時候都會被觸發,但是它不能讓你阻止應用程式的關閉。