線程是一個獨立的運行單元,每一個進程內部都有多個線程,每一個線程均可以各自同時執行指令。每一個線程都有本身獨立的棧,可是與進程內的其餘線程共享內存。可是對於.NET的客戶端程序(Console,WPF,WinForms)是由CLR建立的單線程(主線程,且只建立一個線程)來啓動。在該線程上能夠建立其餘線程。html
圖:
編程
多線程由內部線程調度程序管理,線程調度器一般是CLR委派給操做系統的函數。線程調度程序確保全部活動線程都被分配到合適的執行時間,線程在等待或阻止時 (例如,在一個獨佔鎖或用戶輸入) 不會消耗 CPU 時間。
在單處理器計算機上,線程調度程序是執行時間切片 — 迅速切換每一個活動線程。在 Windows 中, 一個時間片是一般數十毫秒爲單位的區域 — — 相比來講 線程間相互切換比CPU更消耗資源。在多處理器計算機上,多線程用一種混合的時間切片和真正的併發性來實現,不一樣的線程會在不一樣的cpu運行代碼。安全
如:cookie
using System; using System.Threading; class ThreadTest { static void Main() { Thread t = new Thread (Write2); // 建立線程t t.Start(); // 執行 Write2() // 同時執行主線程上的該方法 for (int i = 0; i < 1000; i++) Console.Write ("1"); } static void Write2() { for (int i = 0; i < 1000; i++) Console.Write ("2"); } } //輸出: //111122221122221212122221212......
在主線程上建立了一個新的線程,該新線程執行WrWrite2方法,在調用t.Start()時,主線程並行,輸出「1」。多線程
圖:
併發
線程Start()以後,線程的IsAlive屬性就爲true,直到該線程結束(當線程傳入的方法結束時,該線程就結束)。異步
如:函數
static void Main() { new Thread (Go).Start(); // 建立一個新線程,並調用Go方法 Go(); // 在主線程上調用Go方法 } static void Go() { // 聲明一個本地局部變量 cycles for (int cycles = 0; cycles < 5; cycles++) Console.Write ('N'); } //輸出: //NNNNNNNNNN (共輸出10個N)
在新線程和主線程上調用Go方法時分別建立了變量cycles,這時cycles在不一樣的線程棧上,因此相互獨立不受影響。性能
圖:
操作系統
若是不一樣線程指向同一個實例的引用,那麼不一樣的線程共享該實例。
如:
class ThreadTest { //全局變量 int i; static void Main() { ThreadTest tt = new ThreadTest(); // 建立一個ThreadTest類的實例 new Thread (tt.Go).Start(); tt.Go(); } // Go方法屬於ThreadTest的實例 void Go() { if (i==1) { ++i; Console.WriteLine (i); } } } //輸出: //2
新線程和主線程上調用了同一個實例的Go方法,因此變量i共享。
靜態變量也能夠被多線程共享
class ThreadTest { static int i; // 靜態變量能夠被線程共享 static void Main() { new Thread (Go).Start(); Go(); } static void Go() { if (i==1) { ++i; Console.WriteLine (i); } } } //輸出: //2
若是將Go方法的代碼位置互換
static void Go() { if (i==1) { Console.WriteLine (i);++i;} } //輸出: //1 //1(有時輸出一個,有時輸出兩個)
若是新線程在Write以後,done=true以前,主線程也執行到了write那麼就會有兩個done。
不一樣線程在讀寫共享字段時會出現不可控的輸出,這就是多線程的線程安全問題。
解決方法: 使用排它鎖來解決這個問題--lock
class ThreadSafe { static bool done; static readonly object locker = new object(); static void Main() { new Thread (Go).Start(); Go(); } static void Go() { //使用lock,確保一次只有一個線程執行該代碼 lock (locker) { if (!done) { Console.WriteLine ("Done"); done = true; } } } }
當多個線程都在爭取這個排它鎖時,一個線程獲取該鎖,其餘線程會處於blocked狀態(該狀態時不消耗cpu),等待另外一個線程釋放鎖時,捕獲該鎖。這就保證了一次
只有一個線程執行該代碼。
Join能夠實現暫停另外一個線程,直到調用Join方法的線程結束。
static void Main() { 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"); } //輸出: //yyyyyy..... Thread t has ended!
線程t調用Join方法,阻塞主線程,直到t線程執行結束,再執行主線程。
Sleep:暫停該線程一段時間
Thread.Sleep (TimeSpan.FromHours (1)); // 暫停一個小時 Thread.Sleep (500); // 暫停500毫秒 Join是暫停別的線程,Sleep是暫停本身線程。
上面的例子是使用Thread類的構造函數,給構造函數傳入一個ThreadStart委託。來實現的。
public delegate void ThreadStart();
而後調用Start方法,來執行該線程。委託執行完該線程也結束。
如:
class ThreadTest { static void Main() { Thread t = new Thread (new ThreadStart (Go)); t.Start(); // 執行Go方法 Go(); // 同時在主線程上執行Go方法 } static void Go() { Console.WriteLine ("hello!"); } }
多數狀況下,能夠不用new ThreadStart委託。直接在構造函數裏傳入void類型的方法。
Thread t = new Thread (Go);
使用lambda表達式
static void Main() { Thread t = new Thread ( () => Console.WriteLine ("Hello!") ); t.Start(); }
默認狀況下建立的線程都是Foreground,只要有一個Foregournd線程在執行,應用程序就不會關閉。
Background線程則不是。一旦Foreground線程執行完,應用程序結束,background就會強制結束。
能夠用IsBackground來查看該線程是什麼類型的線程。
public static void Main() { try { new Thread (Go).Start(); } catch (Exception ex) { // 不能捕獲異常 Console.WriteLine ("Exception!"); } } static void Go() { throw null; } //拋出 Null異常
此時並不能在Main方法裏捕獲線程Go方法的異常,若是是Thread自身的異常能夠捕獲。
正確捕獲方式:
public static void Main() { new Thread (Go).Start(); } static void Go() { try { // ... throw null; // 這個異常會被下面捕獲 // ... } catch (Exception ex) { // ... } }
當建立一個線程時,就會消耗幾百毫秒cpu,建立一些新的私有局部變量棧。每一個線程還消耗(默認)約1 MB的內存。線程池經過共享和回收線程,容許在不影響性能的狀況下啓用多線程。
每一個.NET程序都有一個線程池,線程池維護着必定數量的工做線程,這些線程等待着執行分配下來的任務。
線程池線程注意點:
1 線程池的線程不能設置名字(致使線程調試困難)。 2 線程池的線程都是background線程 3 阻塞一個線程池的線程,會致使延遲。 4 能夠隨意設置線程池的優先級,在回到線程池時改線程就會被重置。
經過Thread.CurrentThread.IsThreadPoolThread.能夠查看該線程是不是線程池的線程。
使用線程池建立線程的方法:
TPL
Framework4.0下可使用Task來建立線程池線程。調用Task.Factory.StartNew(),傳遞一個委託
static void Main() { Task.Factory.StartNew (Go); } static void Go() { Console.WriteLine ("Hello from the thread pool!"); }
Task.Factory.StartNew 返回一個Task對象。能夠調用該Task對象的Wait來等待該線程結束,調用Wait時會阻塞調用者的線程。
static void Main() { Task t=new Task(Go); t.Start(); } static void Go() { Console.WriteLine ("Hello from the thread pool!"); }
直接調用Task.Run傳入方法,執行。
static void Main() { Task.Run(() => Go()); } static void Go() { Console.WriteLine ("Hello from the thread pool!"); }
QueueUserWorkItem沒有返回值。使用 QueueUserWorkItem,只需傳遞相應委託的方法就行。
static void Main() { //Go方法的參數data此時爲空 ThreadPool.QueueUserWorkItem (Go); //Go方法的參數data此時爲123 ThreadPool.QueueUserWorkItem (Go, 123); Console.ReadLine(); } static void Go (object data) { Console.WriteLine ("Hello from the thread pool! " + data); }
委託異步能夠返回任意類型個數的值。
使用委託異步的方式:
如:
static void Main() { Func<string, int> method = Work; IAsyncResult cookie = method.BeginInvoke ("test", null, null); // // ... 此時能夠同步處理其餘事情 // int result = method.EndInvoke (cookie); Console.WriteLine ("String length is: " + result); } static int Work (string s) { return s.Length; }
使用回調函數來簡化委託的異步調用,回調函數參數爲IAsyncResult類型
static void Main() { Func<string, int> method = Work; method.BeginInvoke ("test", Done, method); // ... //並行其餘事情 } static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie) { var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result); }
使用匿名方法
Func<string, int> f = s => { return s.Length; }; f.BeginInvoke("hello", arg => { var target = (Func<string, int>)arg.AsyncState; int result = target.EndInvoke(arg); Console.WriteLine("String length is: " + result); }, f);
Thread構造函數傳遞方法有兩種方式:
public delegate void ThreadStart(); public delegate void ParameterizedThreadStart (object obj);
因此Thread能夠傳遞零個或一個參數,可是沒有返回值。
static void Main() { Thread t = new Thread ( () => Print ("Hello from t!") ); t.Start(); } static void Print (string message) { Console.WriteLine (message); }
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); }
Lambda簡潔高效,可是在捕獲變量的時候要注意,捕獲的變量是否共享。
如:
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start(); //輸出: //0223447899
由於每次循環中的i都是同一個i,是共享變量,在輸出的過程當中,i的值會發生變化。
解決方法-局部域變量
for (int i = 0; i < 10; i++) { int temp = i; new Thread (() => Console.Write (temp)).Start(); }
這時每一個線程都指向新的域變量temp(此時每一個線程都有屬於本身的花括號的域變量)在該線程中temp不受其餘線程影響。
委託能夠有任意個傳入和輸出參數。以Action,Func來舉例。
Func<string, int> method = Work; IAsyncResult cookie = method.BeginInvoke("test", null, null); // // ... 此時能夠同步處理其餘事情 // int result = method.EndInvoke(cookie); Console.WriteLine("String length is: " + result); int Work(string s) { return s.Length; }
使用回調函數獲取返回值
static void Main() { Func<string, int> method = Work; method.BeginInvoke ("test", Done, null); // ... //並行其餘事情 } static int Work (string s) { return s.Length; } static void Done (IAsyncResult cookie) { var target = (Func<string, int>) cookie.AsyncState; int result = target.EndInvoke (cookie); Console.WriteLine ("String length is: " + result); }
EndInvoke作了三件事情:
Task
如:
static void Main() { // 建立Task並執行 Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http://www.baidu.com") ); // 同時執行其餘方法 Console.WriteLine("begin"); //等待獲取返回值,而且不會阻塞主線程 Console.WriteLine(task.Result); Console.WriteLine("end"); } static string DownloadString (string uri) { using (var wc = new System.Net.WebClient()) return wc.DownloadString (uri); }
參考: