天天看點

關于Thread的那些事 關于Thread的那些事

關于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,雖然這個事件在任何未處理異常抛出的時候都會被觸發,但是它不能讓你阻止應用程式的關閉。