在講多線程前。先回憶下異步委托:
/*
異步委托自我解釋:即用.net委托來自動創建次線程(子線程)以處理異步方法的調用
* 當調用BeginInvoke()方法的時候,程序就會自動創建一個子線程去處理異步委托的方法。
*/
//線程被定義為可執行應用程序中的基本執行單元
1 //1:System.Threading 命名空間包含多種類型,可以使用它們來創建多線程應用程序,Thread類是核心,它代表了某個給定的線程, 2 //若想要得到當前(正在執行某段代碼)線程的引用:比如: 3 //得到正在執行這個方法的線程 4 Thread curr = Thread.CurrentThread; 5 //2:在.net平台下,應用程序域和線程之間並不是一 一對應的。事實上,在任何時間,一個應用程序域內都可能有多個線程, 6 //而且,一個特定的線程在它的生命周期內不一定被限定在一個應用程序域中,Windows線程調度程序和CLR會根據需要讓線程能夠自由地跨越應用程序域的邊界。 7 //雖然活動的線程能夠跨越多個應用程序域邊界,但是在任何一個時間點上,一個線程只能允許在一個應用程序域中,也就是說一個線程同時在多個應用程序域上執行任務是不可能的。 8 9 //獲取正在承載當前線程的應用程序域 10 AppDomain ad = Thread.GetDomain(); 11 12 //3:在任何特定的時刻,一個線程也可以移動到一個特定的上下文中。並且它可以有CLR重新部署在一個新的上下文中, 13 //獲取當前操作線程的上下文 Context所屬命名空間:System.Runtime.Remoting.Contexts 14 Context ctx = Thread.CurrentContext; 15 Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
//開啟一個子線程異步調用委托 //IAsyncResult r = a.BeginInvoke(9, 10, null, null); ////模擬等待 : IsCompleted判斷異步操作是否完成 //while (!r.IsCompleted) //{ // /* // 可以在此處 做當異步請求沒完成之前的邏輯 // */ // Console.WriteLine("異步請求未完成。。。。。"); // Thread.Sleep(1000); //} ////Console.WriteLine(a(1, 2)); //Console.WriteLine("over"); //int c = a.EndInvoke(r); ////以下的代碼必須要等Result()執行完成才能執行,所以這些代碼可以放到IsCompleted判斷中 //Console.WriteLine("代碼結束"); //Console.WriteLine(c);
/*
主線程, 調用線程在BeginInvoke()完成之前就被阻塞了
* 即:EndInvoke()后面代碼要等BeginInvoke()完成后。才能(主線程)執行,
* 這樣效率顯然不高。可以用IsCompleted搞定
*/
IsCompleted不是最高效的方式,因為它時時刻刻都會監聽異步委托(子線程)是否完成,
/*
* 如果能在異步委托(子線程)完成時,子線程主動通知主線程那是不是更好呢?
* 如果想實現,則在調用BenginInvoke()的時候提供一個System.AsyncCallback委托的實例做完參數,這個參數默認是null,只要提供AsyncCallback對象
* 當異步調用完成時,子線程將自動調用AsyncCallback對象指定的方法
*/
IAsyncCallbac委托的作用
比如可以定義
//當前異步是否完成
private static bool isDone = false;
1 static void AddComplete(IAsyncResult r) 2 { 3 Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 4 Console.WriteLine("Complete....."); 5 6 //using System.Runtime.Remoting.Messaging; 7 AsyncResult result = r as AsyncResult; 8 //AsyncDelegate:獲取在其上調用的委托對象 9 Add a = (Add)result.AsyncDelegate; 10 Console.WriteLine("結果是:{0}", a.EndInvoke(r)); 11 12 13 /* 14 獲取BeginInvoke傳來的額外數據(即異步操作的信息),可以使用IAsyncResult參數的AsyncState屬性。 15 * 因為是object類型。所以這里邀請顯示強制 轉換,所以必須知道實際的類型 16 */ 17 //null值判斷 18 if (r.AsyncState != null) 19 { 20 //獲取消息對象,並轉換成string 21 string msg = r.AsyncState.ToString(); 22 Console.WriteLine("異步委托傳來的消息是:{0}", msg); 23 } 24 25 26 isDone = true; 27 }
編寫測試代碼
IAsyncResult result = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), null); //a.BeginInvoke(9, 9, AddComplete, null);//簡寫方式 //這里可以做其他事情,等異步委托完成會自動調用 AddComplete 方法 while (!isDone) { Console.WriteLine("異步委還沒完成"); Thread.Sleep(3000); }
/*
從是上面可以看出,BeginInvoke()方法最后一個參數一直都是傳的null,其實默認值也是null,
* 該參數允許主線程傳遞額外的狀態信息給回調方法,因為這個參數的類型是System.object,所以可以傳入
* 任何回調方法,所希望的類型的數據,現在我們可以給AddComplete()傳入一個自定義文本消息
* 要在AddComplete()獲取數據,可以使用IAsyncResult參數的AsyncState屬性。
*/
IAsyncResult result1 = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), "異步委托信息");
system.Threading與線程交互, system.Threading中部分類型
/* * Interlocked:為被多個線程共享訪問的類型提供原子操作 * Monitor:使用鎖定和特等信息來同步線程對象,C#的lock關鍵字在后台使用的就是Monitor對象 * Mutex:互斥體,可以用於應用程序域邊界之間的同步 * ParamtetrizedThreadStart:委托,它允許線程調用包含任意多個參數的方法 * Semaphore:用於限制對一個資源或一類資源的並發訪問的線程數量 * Thread:代表CLR中執行的線程,使用這個類型,能夠在初始的應用程序域中創建額外的線程 * ThreadPool:用於和一個進程中的(有CLR維護的)線程池交互 * ThreadPriority:代表了線程調度的優先級別(Highest,Normal等) * ThreadStart:該委托用於定義一個線程所調用的方法,和ParameterizedThreadStart委托不同,這個方法的目標必須符合一種固定的原型 * ThreadState:代表線程處於的狀態(Running,Aborted等) * Timer:提供以指定的時間間隔執行方法的機制 * TimerCallback:該委托類型應與Timer類型一起使用 */ //system.Threading.Thread類 /* * system.Threading命名空間中最基本的類型是Thread,它是一個面向對象的包裝器,包裝特定應用程序域中某個執行單元,Thread類型中定義了 * 許多方法(包括靜態的和共享的)。使用這些方法能夠在當前應用程序域中創建、掛起、停止、和銷毀線程 */ //Thread類主要靜態成員 /* * CurrentContext:只讀屬性,返回當前線程的上下文 * CurrentThread:只讀屬性,返回當前線程的應用 * GetDomain()和GetDomainID():返回當前應用程序域的應用或當前線程正在運行的域的ID * Sleep():將當前線程 掛起指定的時間 */ //Thread類主要實例級成員 /* * IsAlive:返回Boolean值,指定線程是否開始了 * IsBackground:獲取或設置一個值,指示線程是否為后台線程 * Name:給線程指定一個友好的名字 * Priority:獲取或設置線程的調度優先級,它是ThreadPriority枚舉中的值之一 * ThreadState:獲取當前線程的狀態,它是ThreadState枚舉的值之一 * Abort():通知CLR盡快銷毀本線程 * Interrupt():中斷當前線程,喚醒處於等待中的線程 * Join():阻塞調用線程,直到某個(調用Join()的)線程終止為止 * Resume():使已掛起的線程繼續執行 * Start():通知CLR盡快執行本線程 * Suspend():掛起當前線程,如果線程已經掛起,則不起作用 */
ThreadStart應用,
測試一個線程和多個線程執行方法的效率問題
編寫測試類
1 class Printer 2 { 3 public void PrintNumbers() 4 { 5 //顯示Thread信息 獲取執行該方法的線程 6 Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name); 7 8 //輸出數字 9 Console.WriteLine("you numbers"); 10 11 for (int i = 0; i < 10; i++) 12 { 13 Console.Write("{0},", i); 14 Thread.Sleep(2000); 15 } 16 Console.WriteLine(); 17 } 18 }
編寫測試代碼
1 /* 2 比如主線程執行一個方法A()方法很耗時間, 3 * 那么現在可以在開一個線程(子線程)同時來執行方法A() 4 * 此時:你必須創建一個指向A()方法的ThreadStart委托,接着把這個委托對象傳給一個新創建的Thread對象的構造函數 5 * 並且調用這個Thread對象的Start()方法以通知CLR:線程已經准備好執行了。 6 */ 7 8 //是單線程和是多線程 9 Console.WriteLine("請選擇多線程或單線程。。。。。"); 10 string threadCount = Console.ReadLine(); 11 12 //命名當前線程 13 Thread t = Thread.CurrentThread; 14 t.Name = "單線程"; 15 16 //線程進程的信息 17 Console.WriteLine("當前線程名稱-->{0}", Thread.CurrentThread.Name); 18 19 //創建執行任務的類 20 Printer p = new Printer(); 21 switch (threadCount) 22 { 23 case "2": 24 //設置線程 25 26 /*這里開啟一個線程去執行PrintNumbers()方法,所以當前線程(也就是主線程)會接着往下面執行 也就是下面的 "代碼段2" 已經執行*/ 27 Thread backgroundThread = new Thread(new ThreadStart(p.PrintNumbers)); 28 backgroundThread.Name = "多線程"; //改變線程名字 29 backgroundThread.Start(); 30 break; 31 case "1": 32 /*由於的單線程,必須等PrintNumbers()方法執行完成 下面的 "代碼段2" 才會執行*/ 33 p.PrintNumbers(); 34 break; 35 default: 36 Console.WriteLine("error"); 37 goto case "1"; 38 //break; 39 } 40 //其他工作 代碼段 2 41 Console.WriteLine("其他業務邏輯");
多線程結果:
單線程結果
ParameterizedThreadStart委托
/*
* ThreadStart委托僅僅指向無返回值,無參數的方法(上面的PrintNumbers()方法)
* 雖然能滿足大多數情況下的要求,但是,如果想把數據傳遞在給子線程上執行的方法,則需要使用ParameterizedThreadStart委托類型,
* ParameterizedThreadStart能夠指向任意帶一個System.Object參數的方法
*/
創建一個測試類
class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } }
測試代碼
//建立AddParams對象,將其傳給子線程 AddParams ap = new AddParams(10, 5); Thread t2 = new Thread(new ParameterizedThreadStart(Addnum)); //Thread t2 = new Thread(Addnum);//簡寫方式 t2.Start(ap);
AutoResetEvent類
/*
* 一個簡單。線程安全的方法是使用AutoResetEvent類,強制線程等待,直到其他線程結束
* 在需要等待的線程中(比如:Main()方法),創建該類的實例,向構造函數傳入False,表示尚未收到通知。
* 然后在需要等待的地方調用WaitOne()方法。
*/
編寫測試類
定義:AutoResetEvent
private static AutoResetEvent waitHandle = new AutoResetEvent(false);
1 AddParams a2 = new AddParams(9, 5); 2 Thread t3 = new Thread(new ParameterizedThreadStart(Addnum)); 3 t3.Start(a2); 4 5 //等待,直到收到通知,通知其他線程結束,才執行下面的代碼 6 waitHandle.WaitOne(); 7 Console.WriteLine("其他線程已經結束");
前台線程和后台線程
//前台線程和后台線程 /* * 前台線程:前台線程能阻止應用程序的終結,一直到所有的前台線程終止后,CLR才能關閉應用程序(即卸載承載的應用程序域) * 后台線程:后台線程(有時候也叫做守護線程)被CLR認為是程序執行中可做出犧牲的途徑,即在任何時候(即使這個線程此時正在執行某項工作)都可以被忽略。 * 因此,如果所有的前台線程終止,當應用程序域卸載時,所有的后台線程也會自動終止。(即在所有前台線程終止后,后台線程也跟着終止了) * 但值得注意的是,前台線程和后台線程並不等同於主線程和工作線程,默認情況下,所有通過Thread.Start()方法創建的線程都自動成為前台線程,這意味着,知道所有 * 的線程本身單元的工作都執行完成了。應用程序域才會卸載。大多情況下,這非常必要。可以用后台線程做一些無關緊要的任務:比如每隔幾分鍾就ping一次郵件服務器看有沒有 * 新郵件的應用程序,或更新當前天氣條件等 */
並發問題以及lock關鍵字
/*
* 在構建多應用程序時,需要確保任何共享數據都處於被保護狀態,以防止多個線程修改它的值,由於一個應用程序域中
* 的所有線程都能夠並發訪問共享數據,所以,想象一下當它們正在訪問其他中的某個數據項時,會發生什么,由於線程調度器會隨機掛起線程,所以如果線程A在完成
* 之前被掛起了。線程B讀到的就是一個不穩定的數據。
*/
創建一個測試類
class showNum { public void PrintNum() { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } }
模擬並發問題:創建類showNum 。調用方法PrintNum()打印數字
1 //創建10個Thread對象的數組,並且每一個對象都調用showNum對象的同一個實例 2 showNum num = new showNum(); 3 ////使10個線程全部指向同一個對象的同一方法 4 Thread[] threads = new Thread[10]; 5 for (int i = 0; i < 10; i++) 6 { 7 threads[i] = new Thread(new ThreadStart(num.PrintNum)); 8 //給線程命名 9 threads[i].Name = string.Format("worker threda #{0}", i); 10 } 11 //現在開始每一個線程 12 foreach (Thread t in threads) 13 { 14 t.Start(); 15 } 16 17 /* 18 * 多運行幾次會發現。每次運行運行的結果明顯不同,在應用程序域中主線程產生了10個工作者進程(子線程) 19 * 每一個工作者線程執行一個PrintNum()方法,由於沒有預防鎖定共享資源,故在PrintNum()輸出到控制台之前,調用PrintNum() 20 * 方法的線程很有可能會被掛起,因為不知道掛起什么時候(或者是否有)可能發生,所以我們得到的是不可預測的結果。 21 * 當每個線程都調用PrintNum()方法的時候,線程調度器可能正在切換線程,這導致了不同的輸出結果,故需要找到一種方式來通過 22 * 編程控制對共享的同步訪問。即lock關鍵字進行同步 23 * 24 * 同步訪問共享資源的首選技術是C#的lock關鍵字,這個關鍵字允許定義一段線程同步的代碼語,采用這項技術,后進入的線程不會中斷當前線程, 25 * 而是停止自身下一步執行。lock關鍵字需要定義一個標記(即一個對象引用),線程在進入鎖定范圍的時候必須獲得這個標記。當試圖鎖定的是一個實例級對象的私有方法時, 26 * 使用方法本身所在對象的引用就可以了。 27 * 如果鎖定公共成員中的一段代碼。比較安全(也比較推薦)的方式是聲明私有的object成員來作為鎖標識 28 */
給PrintNum()方法加上lock關鍵字
定義一個鎖標識
private object threadLock = new object();//鎖標識
應用
public void PrintNum() { //使用鎖標識 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } }
運行后。效果是很明顯的
lock關鍵字同步的兩種方式,這里全部歸納在類 showNum 中。
class showNum { //lock關鍵字同步的兩種方式 /* * 這樣就有效的設計了一個保證當前線程完成任務的方法,一旦一個線程進入鎖定范圍,在它退出鎖定范圍且釋放鎖定之前, * 其他線程將無法訪問鎖定標記(本例是當前對象的引用)。因此,如果線程A獲得鎖定標記,直到它放棄這個鎖定標記,其它線程才能夠進入鎖定范圍。 */ //第一種 //如果鎖定公共成員中的一段代碼。比較安全(也比較推薦)的方式是聲明私有的object成員來作為鎖標識 private object threadLock = new object();//鎖標識 public void PrintNum() { //使用鎖標識 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } //第二種 //試圖鎖定的是一個實例級對象的私有方法,使用本身所在對象的引用就可以了 private void Show() { //使用當前對象作為線程標記 lock (this) { //所以在這范圍內的代碼是線程安全的 } } //第三種 //試圖鎖定一個靜態方法中的代碼,只需要聲明一個私有靜態對象成員變量作為鎖定標記就可以了 private static object staticLock = new object(); public static void showSt() { lock (staticLock) { //所以在這范圍內的代碼是線程安全的 } } }
擴展知識:System.Threading.Monitor
Monitor類型進行同步跟lock沒有實質上的區別
C# lock聲明實際上是和System.Threading.Monitor類一同使用時的速記符號。經過編輯器的處理,鎖定區域實際上被轉化成了如下內容(Reflector查看)
比如上面的PrintNum()方法最后是這樣的
* public void PrintNum() { Monitor.Enter(threadLock); try { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } catch (Exception) { Monitor.Exit(threadLock); } } * * 首先請注意:Monitor.Enter()是線程標記的最終容器,而該線程標記作為參數由用戶指定給lock關鍵字,接下來,所有在鎖定范圍中的代碼 * 被try塊包含。而對應的finally子句保證了無論出現什么運行錯誤,線程標記都能被釋放(通過Monitor.Exit()方法)。如果程序直接用Monitor類型同步(不用lock關鍵字) * 看到的結果是一樣的。那Monitor類型好處何在?一句話:更好的控制能力。使用Monitor類型,可以使用Monitor。Wait()方法,指示活動的線程等待一段時間,在當前線程完成 * 操作時,使用Monitor.Pulse()或Monitor.PulseAll()通知等待的線程。但大多時候都是用lock就足夠了。
使用System.Threading.Interlocked類型進行同步
* 成員:
* CompareExchange()==>安全地比較兩個值是否相等,如果相等,用第3個值改變第一個值。
* Decrement()==> 安全遞減1
* Exchange()==> 安全地交換數據
* Increment()==>安全遞加1
雖然不太起眼,但是原子型地修改單個值在多線程環境下是非常普遍的,假如有個方法名為AddOne(),用它來給名為intVal的整型變量加1,如:
* public void AddOne()
* {
* lock(myLockToken)
* {
* intVal++;
* }
* }
* 可以通過靜態的Interlocked.Increment()方法簡化代碼,以引用方式傳入要遞增的變量,注意:Increment()方法不但可以修改傳入的參數值。還會返回遞增后的新值
* public void Addone()
{
int newv = Interlocked.Increment(ref intVal);
}
* 除了使用Increment()和Decrement(),使用Interlocked類型還可以把原子型地賦值給數字對象。例如:想把83賦給一個成員變量,無須明確使用lock關鍵字或者Monitor邏輯,
* 使用Interlocked.Exchange()即可
* public void Addone()
{
Interlocked.Exchange(ref intVal,83);
}
* 最后,如果想通過線程安全的情況下測試兩個值是否相等來改變比較后的指向,可以使用Interlocked.CompareExchange();
* public void Addone()
{
* 如果intVal等於83,把99賦值給intVal
Interlocked.CompareExchange(ref intVal,99,83);
}
使用[Synchronization]特性進行同步
* 最后一個同步化原語是[Synchronization]特性,它位於:System.Runtime.Remoting.Contexts;
* 這個類級別的特性有效地使對象的所有實例的成員都保持線程安全。當CLR分配帶[Synchronization]的對象時,它會把這個對象放在同步上下文中。
* 要想對象不被在上下文中移動,就必須讓繼承ContextBoundObject
比如:
[Synchronization] class AddParams : ContextBoundObject { }
* //showNum全部方法都是線程安全的 * [Synchronization] class showNum:ContextBoundObject { * public void PrintNum() { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } * * 像這樣寫線程安全的代碼是一種"偷懶的"方式。因為它不需要我們實際深入線程控制敏感數據的細節,這種方式的主要問題是:即使一個方法沒有使用線程敏感的數據,CLR仍然會鎖定 * 該方法的調用,很明顯,這樣會全面降低性能,所以要小心使用這種方式。
TimerCallback編程
* 許多程序需要定期調用具體的方法,比如:可能有一個應用程序需要在狀態欄上通過一個輔助函數顯示當前的時間
* 或者,可能希望應用程序調用一個輔助函數,讓它經常執行非常緊迫的后台任務,
* 比如:檢查是否收到新郵件,像這些情況,就可以使用System.Threading.Timer類型和與其相關的TimerCallback委托
編寫測試方法
/* * PrintTime(object state)方法沒有返回值,有一個object類型的參數, * 因為TimerCallback僅僅調用符合這樣的簽名方法。傳入TimerCallback委托的參數可以是任何信息,還需要注意的是: * 由於這個參數是System.object類型,所以可以使用Ssystem.Array或者自定義類,結構傳入多個值 */ static void PrintTime(object state) { Console.WriteLine("Time is {0}", DateTime.Now.ToLongTimeString()); if (state != null) Console.WriteLine("消息是 {0}", state); }
測試代碼
//定義一個TimerCallback委托實例,並把它傳入Timer對象中,除了定義TimerCallback委托,Timer的構造函數還允許定義別的信息傳送到委托指定的方法中 TimerCallback timeCB = new TimerCallback(PrintTime); Timer tm = new Timer( timeCB, //TimerCallback委托對象 90, //想傳入的參數 (null表示沒有參數) 0, //在開始之前,等待多長時間執行委托指向的方法(以毫秒為單位) 1000 //每次調用的間隔時間(以毫秒為單位) );
CLR線程池
/*
* 關於線程的核心主題是CLR線程池,當使用委托類型(通過BeginInvoke()方法)進行異步方法調用的時候,CLR並不會創建新的線程,為了取得更高的效率,委托的BeginInvoke()方法
* 創建了由運行時維護的工作者進程池,為了更好地和這些線程進行交互,System.Threading命名空間提供了ThreadPool類類型
* 如果想使用線程池中的工作者線程排隊執行一個方法,可以使用ThreadPool.QueueUserWorkItem()方法,這個方法進行了重載,
* 除了可以傳遞一個WaitCallback委托之外還可以指定一個可選的表示自定義狀態數據的System.object。
*/
還記得上面 10 次調用showNum類中PrintNum方法的代碼嗎?
現在用線程池實現
創建一個跟WaitCallback委托簽名的方法 p(),把showNum對象傳給p(),在里面調用PrintNum()
//,我們不需要手工創建Thread對象數組,只要把線程池指向 p()方法即可
static void p(object state) { showNum n = state as showNum; n.PrintNum(); }
測試代碼
WaitCallback workItem = new WaitCallback(p); showNum num = new showNum(); for (int i = 0; i < 10; i++) { //線程池中的線程總是后台線程 ThreadPool.QueueUserWorkItem(workItem, num); }
/*
* 可以對比一下顯示創建Thread對象和使用這個CLR所維護的線程池的好處何在:
*
* 使用線程持的主要好處是:
* 1:線程池減少了線程創建,開始和停止的次數,而這提高了效率。
* 2:使用線程池,能夠使我們將注意力放到業務邏輯上而不是多線程架構上。
* 然而,在某些情況下應優先使用手工創建線程,比如:
* 1:如果需要前台線程或設置優先級別。線程池中的線程總是后台線程,且它的優先級是默認的(ThreadPriority.Normal)
* 2:如果需要有一個帶有固定標識的線程便於退出,掛起,或通過名字找到它
*/
最后:以下是全部測試源碼,可以復制到自己的vs中運行,一個#region是一個知識模塊,可以先全部注釋,一個一個進行測試即可
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Runtime.Remoting.Contexts; using System.Runtime.Remoting.Messaging; namespace ThreadDemo { class Program { private static AutoResetEvent waitHandle = new AutoResetEvent(false); /* 異步委托自我解釋:即用.net委托來自動創建次線程(子線程)以處理異步方法的調用 * 當調用BeginInvoke()方法的時候,程序就會自動創建一個子線程去處理異步委托的方法。 */ public delegate int Add(int x, int y); //當前異步是否完成 private static bool isDone = false; //線程被定義為可執行應用程序中的基本執行單元 static void Main(string[] args) { //1:System.Threading 命名空間包含多種類型,可以使用它們來創建多線程應用程序,Thread類是核心,它代表了某個給定的線程, //若想要得到當前(正在執行某段代碼)線程的引用:比如: //得到正在執行這個方法的線程 Thread curr = Thread.CurrentThread; //2:在.net平台下,應用程序域和線程之間並不是一 一對應的。事實上,在任何時間,一個應用程序域內都可能有多個線程, //而且,一個特定的線程在它的生命周期內不一定被限定在一個應用程序域中,Windows線程調度程序和CLR會根據需要讓線程能夠自由地跨越應用程序域的邊界。 //雖然活動的線程能夠跨越多個應用程序域邊界,但是在任何一個時間點上,一個線程只能允許在一個應用程序域中,也就是說一個線程同時在多個應用程序域上執行任務是不可能的。 //獲取正在承載當前線程的應用程序域 AppDomain ad = Thread.GetDomain(); //3:在任何特定的時刻,一個線程也可以移動到一個特定的上下文中。並且它可以有CLR重新部署在一個新的上下文中, //獲取當前操作線程的上下文 Context所屬命名空間:System.Runtime.Remoting.Contexts Context ctx = Thread.CurrentContext; //Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Add a = new Add(Result); /* 主線程, 調用線程在BeginInvoke()完成之前就被阻塞了 * 即:EndInvoke()后面代碼要等BeginInvoke()完成后。才能(主線程)執行, * 這樣效率顯然不高。可以用IsCompleted搞定 */ #region MyRegion //開啟一個子線程異步調用委托 //IAsyncResult r = a.BeginInvoke(9, 10, null, null); ////模擬等待 : IsCompleted判斷異步操作是否完成 //while (!r.IsCompleted) //{ // /* // 可以在此處 做當異步請求沒完成之前的邏輯 // */ // Console.WriteLine("異步請求未完成。。。。。"); // Thread.Sleep(1000); //} ////Console.WriteLine(a(1, 2)); //Console.WriteLine("over"); //int c = a.EndInvoke(r); ////以下的代碼必須要等Result()執行完成才能執行,所以這些代碼可以放到IsCompleted判斷中 //Console.WriteLine("代碼結束"); //Console.WriteLine(c); #endregion #region IAsyncCallbac委托的作用 //IAsyncCallbac委托的作用 /* IsCompleted不是最高效的方式,因為它時時刻刻都會監聽異步委托(子線程)是否完成, * 如果能在異步委托(子線程)完成時,子線程主動通知主線程那是不是更好呢? * 如果想實現,則在調用BenginInvoke()的時候提供一個System.AsyncCallback委托的實例做完參數,這個參數默認是null,只要提供AsyncCallback對象 * 當異步調用完成時,子線程將自動調用AsyncCallback對象指定的方法 */ //IAsyncResult result = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), null); //a.BeginInvoke(9, 9, AddComplete, null);//簡寫方式 //這里可以做其他事情,等異步委托完成會自動調用 AddComplete 方法 //while (!isDone) //{ // Console.WriteLine("異步委還沒完成"); // Thread.Sleep(3000); //} #endregion #region 傳遞和接收自定義數據 //傳遞和接收自定義數據 /* 從是上面可以看出,BeginInvoke()方法最后一個參數一直都是傳的null,其實默認值也是null, * 該參數允許主線程傳遞額外的狀態信息給回調方法,因為這個參數的類型是System.object,所以可以傳入 * 任何回調方法,所希望的類型的數據,現在我們可以給AddComplete()傳入一個自定義文本消息 * 要在AddComplete()獲取數據,可以使用IAsyncResult參數的AsyncState屬性。 */ //IAsyncResult result1 = a.BeginInvoke(10, 3, new AsyncCallback(AddComplete), "異步委托信息"); #endregion //system.Threading與線程交互, system.Threading中部分類型 /* * Interlocked:為被多個線程共享訪問的類型提供原子操作 * Monitor:使用鎖定和特等信息來同步線程對象,C#的lock關鍵字在后台使用的就是Monitor對象 * Mutex:互斥體,可以用於應用程序域邊界之間的同步 * ParamtetrizedThreadStart:委托,它允許線程調用包含任意多個參數的方法 * Semaphore:用於限制對一個資源或一類資源的並發訪問的線程數量 * Thread:代表CLR中執行的線程,使用這個類型,能夠在初始的應用程序域中創建額外的線程 * ThreadPool:用於和一個進程中的(有CLR維護的)線程池交互 * ThreadPriority:代表了線程調度的優先級別(Highest,Normal等) * ThreadStart:該委托用於定義一個線程所調用的方法,和ParameterizedThreadStart委托不同,這個方法的目標必須符合一種固定的原型 * ThreadState:代表線程處於的狀態(Running,Aborted等) * Timer:提供以指定的時間間隔執行方法的機制 * TimerCallback:該委托類型應與Timer類型一起使用 */ //system.Threading.Thread類 /* * system.Threading命名空間中最基本的類型是Thread,它是一個面向對象的包裝器,包裝特定應用程序域中某個執行單元,Thread類型中定義了 * 許多方法(包括靜態的和共享的)。使用這些方法能夠在當前應用程序域中創建、掛起、停止、和銷毀線程 */ //Thread類主要靜態成員 /* * CurrentContext:只讀屬性,返回當前線程的上下文 * CurrentThread:只讀屬性,返回當前線程的應用 * GetDomain()和GetDomainID():返回當前應用程序域的應用或當前線程正在運行的域的ID * Sleep():將當前線程 掛起指定的時間 */ //Thread類主要實例級成員 /* * IsAlive:返回Boolean值,指定線程是否開始了 * IsBackground:獲取或設置一個值,指示線程是否為后台線程 * Name:給線程指定一個友好的名字 * Priority:獲取或設置線程的調度優先級,它是ThreadPriority枚舉中的值之一 * ThreadState:獲取當前線程的狀態,它是ThreadState枚舉的值之一 * Abort():通知CLR盡快銷毀本線程 * Interrupt():中斷當前線程,喚醒處於等待中的線程 * Join():阻塞調用線程,直到某個(調用Join()的)線程終止為止 * Resume():使已掛起的線程繼續執行 * Start():通知CLR盡快執行本線程 * Suspend():掛起當前線程,如果線程已經掛起,則不起作用 */ #region ThreadStart /* 比如主線程執行一個方法A()方法很耗時間, * 那么現在可以在開一個線程(子線程)同時來執行方法A() * 此時:你必須創建一個指向A()方法的ThreadStart委托,接着把這個委托對象傳給一個新創建的Thread對象的構造函數 * 並且調用這個Thread對象的Start()方法以通知CLR:線程已經准備好執行了。 */ //是單線程和是多線程 //Console.WriteLine("請選擇多線程或單線程。。。。。"); //string threadCount = Console.ReadLine(); ////命名當前線程 //Thread t = Thread.CurrentThread; //t.Name = "單線程"; ////線程進程的信息 //Console.WriteLine("當前線程名稱-->{0}", Thread.CurrentThread.Name); ////創建執行任務的類 //Printer p = new Printer(); //switch (threadCount) //{ // case "2": // //設置線程 // /*這里開啟一個線程去執行PrintNumbers()方法,所以當前線程(也就是主線程)會接着往下面執行 也就是下面的 "代碼段2" 已經執行*/ // Thread backgroundThread = new Thread(new ThreadStart(p.PrintNumbers)); // backgroundThread.Name = "多線程"; //改變線程名字 // backgroundThread.Start(); // break; // case "1": // /*由於的單線程,必須等PrintNumbers()方法執行完成 下面的 "代碼段2" 才會執行*/ // p.PrintNumbers(); // break; // default: // Console.WriteLine("error"); // goto case "1"; // //break; //} ////其他工作 代碼段 2 //Console.WriteLine("其他業務邏輯"); #endregion #region ParameterizedThreadStart //ParameterizedThreadStart委托 /* * ThreadStart委托僅僅指向無返回值,無參數的方法(上面的PrintNumbers()方法) * 雖然能滿足大多數情況下的要求,但是,如果想把數據傳遞在給子線程上執行的方法,則需要使用ParameterizedThreadStart委托類型, * ParameterizedThreadStart能夠指向任意帶一個System.Object參數的方法 */ //建立AddParams對象,將其傳給子線程 //AddParams ap = new AddParams(10, 5); //Thread t2 = new Thread(new ParameterizedThreadStart(Addnum)); ////Thread t2 = new Thread(Addnum);//簡寫方式 //t2.Start(ap); #endregion #region AutoResetEvent //AutoResetEvent類 /* * 一個簡單。線程安全的方法是使用AutoResetEvent類,強制線程等待,直到其他線程結束 * 在需要等待的線程中(比如:Main()方法),創建該類的實例,向構造函數傳入False,表示尚未收到通知。 * 然后在需要等待的地方調用WaitOne()方法。 */ //AddParams a2 = new AddParams(9, 5); //Thread t3 = new Thread(new ParameterizedThreadStart(Addnum)); //t3.Start(a2); ////等待,直到收到通知,通知其他線程結束,才執行下面的代碼 //waitHandle.WaitOne(); //Console.WriteLine("其他線程已經結束"); #endregion #region 前台線程和后台線程 //前台線程和后台線程 /* * 前台線程:前台線程能阻止應用程序的終結,一直到所有的前台線程終止后,CLR才能關閉應用程序(即卸載承載的應用程序域) * 后台線程:后台線程(有時候也叫做守護線程)被CLR認為是程序執行中可做出犧牲的途徑,即在任何時候(即使這個線程此時正在執行某項工作)都可以被忽略。 * 因此,如果所有的前台線程終止,當應用程序域卸載時,所有的后台線程也會自動終止。(即在所有前台線程終止后,后台線程也跟着終止了) * 但值得注意的是,前台線程和后台線程並不等同於主線程和工作線程,默認情況下,所有通過Thread.Start()方法創建的線程都自動成為前台線程,這意味着,知道所有 * 的線程本身單元的工作都執行完成了。應用程序域才會卸載。大多情況下,這非常必要。可以用后台線程做一些無關緊要的任務:比如每隔幾分鍾就ping一次郵件服務器看有沒有 * 新郵件的應用程序,或更新當前天氣條件等 */ #endregion #region 並發問題以及lock關鍵字 //並發問題 /* * 在構建多應用程序時,需要確保任何共享數據都處於被保護狀態,以防止多個線程修改它的值,由於一個應用程序域中 * 的所有線程都能夠並發訪問共享數據,所以,想象一下當它們正在訪問其他中的某個數據項時,會發生什么,由於線程調度器會隨機掛起線程,所以如果線程A在完成 * 之前被掛起了。線程B讀到的就是一個不穩定的數據。 */ //模擬並發問題:創建類showNum 。調用方法PrintNum()打印數字 //創建10個Thread對象的數組,並且每一個對象都調用showNum對象的同一個實例 //showNum num = new showNum(); //////使10個線程全部指向同一個對象的同一方法 //Thread[] threads = new Thread[10]; //for (int i = 0; i < 10; i++) //{ // threads[i] = new Thread(new ThreadStart(num.PrintNum)); // //給線程命名 // threads[i].Name = string.Format("worker threda #{0}", i); //} ////現在開始每一個線程 //foreach (Thread t in threads) //{ // t.Start(); //} /* * 多運行幾次會發現。每次運行運行的結果明顯不同,在應用程序域中主線程產生了10個工作者進程(子線程) * 每一個工作者線程執行一個PrintNum()方法,由於沒有預防鎖定共享資源,故在PrintNum()輸出到控制台之前,調用PrintNum() * 方法的線程很有可能會被掛起,因為不知道掛起什么時候(或者是否有)可能發生,所以我們得到的是不可預測的結果。 * 當每個線程都調用PrintNum()方法的時候,線程調度器可能正在切換線程,這導致了不同的輸出結果,故需要找到一種方式來通過 * 編程控制對共享的同步訪問。即lock關鍵字進行同步 * * 同步訪問共享資源的首選技術是C#的lock關鍵字,這個關鍵字允許定義一段線程同步的代碼語,采用這項技術,后進入的線程不會中斷當前線程, * 而是停止自身下一步執行。lock關鍵字需要定義一個標記(即一個對象引用),線程在進入鎖定范圍的時候必須獲得這個標記。當試圖鎖定的是一個實例級對象的私有方法時, * 使用方法本身所在對象的引用就可以了。 * 如果鎖定公共成員中的一段代碼。比較安全(也比較推薦)的方式是聲明私有的object成員來作為鎖標識 */ #endregion #region System.Threading.Monitor //使用System.Threading.Monitor類型進行同步 /* * C# lock聲明實際上是和System.Threading.Monitor類一同使用時的速記符號。經過編輯器的處理,鎖定區域實際上被轉化成了如下內容(Reflector查看) * * * public void PrintNum() { Monitor.Enter(threadLock); try { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } catch (Exception) { Monitor.Exit(threadLock); } } * * 首先請注意:Monitor.Enter()是線程標記的最終容器,而該線程標記作為參數由用戶指定給lock關鍵字,接下來,所有在鎖定范圍中的代碼 * 被try塊包含。而對應的finally子句保證了無論出現什么運行錯誤,線程標記都能被釋放(通過Monitor.Exit()方法)。如果程序直接用Monitor類型同步(不用lock關鍵字) * 看到的結果是一樣的。那Monitor類型好處何在?一句話:更好的控制能力。使用Monitor類型,可以使用Monitor。Wait()方法,指示活動的線程等待一段時間,在當前線程完成 * 操作時,使用Monitor.Pulse()或Monitor.PulseAll()通知等待的線程。但大多時候都是用lock就足夠了。 * */ #endregion #region Interlocked //使用System.Threading.Interlocked類型進行同步 /* * 成員: * CompareExchange()==>安全地比較兩個值是否相等,如果相等,用第3個值改變第一個值。 * Decrement()==> 安全遞減1 * Exchange()==> 安全地交換數據 * Increment()==>安全遞加1 * * * 雖然不太起眼,但是原子型地修改單個值在多線程環境下是非常普遍的,假如有個方法名為AddOne(),用它來給名為intVal的整型變量加1,如: * public void AddOne() * { * lock(myLockToken) * { * intVal++; * } * } * 可以通過靜態的Interlocked.Increment()方法簡化代碼,以引用方式傳入要遞增的變量,注意:Increment()方法不但可以修改傳入的參數值。還會返回遞增后的新值 * public void Addone() { int newv = Interlocked.Increment(ref intVal); } * 除了使用Increment()和Decrement(),使用Interlocked類型還可以把原子型地賦值給數字對象。例如:想把83賦給一個成員變量,無須明確使用lock關鍵字或者Monitor邏輯, * 使用Interlocked.Exchange()即可 * public void Addone() { Interlocked.Exchange(ref intVal,83); } * 最后,如果想通過線程安全的情況下測試兩個值是否相等來改變比較后的指向,可以使用Interlocked.CompareExchange(); * public void Addone() { * 如果intVal等於83,把99賦值給intVal Interlocked.CompareExchange(ref intVal,99,83); } */ //Inter cy = new Inter(); //Thread t5 = new Thread(cy.Addone); //t5.Start(); //for (int i = 0; i < 50; i++) //{ // Thread t6 = new Thread(cy.Addone); // t6.Start(); //} #endregion //使用[Synchronization]特性進行同步 /* * 最后一個同步化原語是[Synchronization]特性,它位於:System.Runtime.Remoting.Contexts; * 這個類級別的特性有效地使對象的所有實例的成員都保持線程安全。當CLR分配帶[Synchronization]的對象時,它會把這個對象放在同步上下文中。 * 要想對象不被在上下文中移動,就必須讓繼承ContextBoundObject * * //showNum全部方法都是線程安全的 * [Synchronization] class showNum:ContextBoundObject { * public void PrintNum() { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } * * 像這樣寫線程安全的代碼是一種"偷懶的"方式。因為它不需要我們實際深入線程控制敏感數據的細節,這種方式的主要問題是:即使一個方法沒有使用線程敏感的數據,CLR仍然會鎖定 * 該方法的調用,很明顯,這樣會全面降低性能,所以要小心使用這種方式。 */ #region TimerCallback //TimerCallback編程 /* * 許多程序需要定期調用具體的方法,比如:可能有一個應用程序需要在狀態欄上通過一個輔助函數顯示當前的時間 * 或者,可能希望應用程序調用一個輔助函數,讓它經常執行非常緊迫的后台任務, * 比如:檢查是否收到新郵件,像這些情況,就可以使用System.Threading.Timer類型和與其相關的TimerCallback委托 * * */ //定義一個TimerCallback委托實例,並把它傳入Timer對象中,除了定義TimerCallback委托,Timer的構造函數還允許定義別的信息傳送到委托指定的方法中 //TimerCallback timeCB = new TimerCallback(PrintTime); //Timer tm = new Timer( // timeCB, //TimerCallback委托對象 // 90, //想傳入的參數 (null表示沒有參數) // 0, //在開始之前,等待多長時間執行委托指向的方法(以毫秒為單位) // 1000 //每次調用的間隔時間(以毫秒為單位) // ); #endregion #region CLR線程池 //CLR線程池 /* * 關於線程的核心主題是CLR線程池,當使用委托類型(通過BeginInvoke()方法)進行異步方法調用的時候,CLR並不會創建新的線程,為了取得更高的效率,委托的BeginInvoke()方法 * 創建了由運行時維護的工作者進程池,為了更好地和這些線程進行交互,System.Threading命名空間提供了ThreadPool類類型 * 如果想使用線程池中的工作者線程排隊執行一個方法,可以使用ThreadPool.QueueUserWorkItem()方法,這個方法進行了重載, * 除了可以傳遞一個WaitCallback委托之外還可以指定一個可選的表示自定義狀態數據的System.object。 */ //模擬上面 10 次調用showNum類中PrintNum方法的代碼,創建一個跟WaitCallback委托簽名的方法 p(),把showNum對象傳給p(),在里面調用PrintNum() //,我們不需要手工創建Thread對象數組,只要把線程池指向 p()方法即可 //WaitCallback workItem = new WaitCallback(p); //showNum num = new showNum(); //for (int i = 0; i < 10; i++) //{ // //線程池中的線程總是后台線程 // ThreadPool.QueueUserWorkItem(workItem, num); //} /* * 可以對比一下顯示創建Thread對象和使用這個CLR所維護的線程池的好處何在: * * 使用線程持的主要好處是: * 1:線程池減少了線程創建,開始和停止的次數,而這提高了效率。 * 2:使用線程池,能夠使我們將注意力放到業務邏輯上而不是多線程架構上。 * 然而,在某些情況下應優先使用手工創建線程,比如: * 1:如果需要前台線程或設置優先級別。線程池中的線程總是后台線程,且它的優先級是默認的(ThreadPriority.Normal) * 2:如果需要有一個帶有固定標識的線程便於退出,掛起,或通過名字找到它 */ #endregion //.NET平台下的並行編程 /* * .NET4發布了一個全新的並行編程庫。使用System.Threading.Tasks中的類型,可以構建細粒度。可擴展的並行代碼。而不必直接與線程和線程池打交道。 * 此外,你還可以使用強類型的LINQ查詢來分配工作 * 總體而言,System.Threading.Tasks中的類型(以及System.Threading中的一些相關類型)被稱為任務並行庫(Task Parallel Library,TPL)。 * TPL使用CLR線程池自動將應用程序的工作動態分配到可用的CPU中,TPL還處理工作分區,線程調度,狀態管理和其他低級別的細節操作。最終的結果是。你可以 * 最大限度地提升.Net應用程序的性能,並且避免直接操作線程所帶來的復雜性。 * 最后,要知道的是,可以這么做並不意味着應該這么做。編寫大量不必要的並行任務會損害.NET程序的性能,同樣創建多線程也會使程序的執行變慢。只有在你的任務真正 * 成為程序性能瓶頸的時候才應該使用TPL,如迭代大量的對象,處理多個文件中的數據等。 */ Console.Read(); } static void p(object state) { showNum n = state as showNum; n.PrintNum(); } /* * PrintTime(object state)方法沒有返回值,有一個object類型的參數, * 因為TimerCallback僅僅調用符合這樣的簽名方法。傳入TimerCallback委托的參數可以是任何信息,還需要注意的是: * 由於這個參數是System.object類型,所以可以使用Ssystem.Array或者自定義類,結構傳入多個值 */ static void PrintTime(object state) { Console.WriteLine("Time is {0}", DateTime.Now.ToLongTimeString()); if (state != null) Console.WriteLine("消息是 {0}", state); } static void AddComplete(IAsyncResult r) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Complete....."); //using System.Runtime.Remoting.Messaging; AsyncResult result = r as AsyncResult; //AsyncDelegate:獲取在其上調用的委托對象 Add a = (Add)result.AsyncDelegate; Console.WriteLine("結果是:{0}", a.EndInvoke(r)); /* 獲取BeginInvoke傳來的額外數據(即異步操作的信息),可以使用IAsyncResult參數的AsyncState屬性。 * 因為是object類型。所以這里邀請顯示強制 轉換,所以必須知道實際的類型 */ //null值判斷 if (r.AsyncState != null) { //獲取消息對象,並轉換成string string msg = r.AsyncState.ToString(); Console.WriteLine("異步委托傳來的消息是:{0}", msg); } isDone = true; } static int Result(int x, int y) { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(3000); return x + y; } //用AddParams類型作為參數,計算每次相加的結果 static void Addnum(object data) { if (data is AddParams) { Console.WriteLine("當前線程ID是:{0}", Thread.CurrentThread.ManagedThreadId); AddParams ap = (AddParams)data; Console.WriteLine("{0}+{1} is {2}", ap.a, ap.b, ap.a + ap.b); } //通知其他線程,該線程已經結束 waitHandle.Set(); } } class Printer { public void PrintNumbers() { //顯示Thread信息 獲取執行該方法的線程 Console.WriteLine("-->{0} is executing PrintNumbers()", Thread.CurrentThread.Name); //輸出數字 Console.WriteLine("you numbers"); for (int i = 0; i < 10; i++) { Console.Write("{0},", i); Thread.Sleep(2000); } Console.WriteLine(); } } class AddParams { public int a, b; public AddParams(int num1, int num2) { a = num1; b = num2; } } class showNum { //lock關鍵字同步的兩種方式 /* * 這樣就有效的設計了一個保證當前線程完成任務的方法,一旦一個線程進入鎖定范圍,在它退出鎖定范圍且釋放鎖定之前, * 其他線程將無法訪問鎖定標記(本例是當前對象的引用)。因此,如果線程A獲得鎖定標記,直到它放棄這個鎖定標記,其它線程才能夠進入鎖定范圍。 */ //第一種 //如果鎖定公共成員中的一段代碼。比較安全(也比較推薦)的方式是聲明私有的object成員來作為鎖標識 private object threadLock = new object();//鎖標識 public void PrintNum() { //Monitor.Enter(threadLock); //try //{ // for (int i = 0; i < 10; i++) // { // //使用線程休眠數秒 // Random r = new Random(); // Thread.Sleep(1000 * r.Next(5)); // Console.Write("{0}, ", i); // } // Console.WriteLine(); //} //catch (Exception) //{ // Monitor.Exit(threadLock); //} //使用鎖標識 lock (threadLock) { for (int i = 0; i < 10; i++) { //使用線程休眠數秒 Random r = new Random(); Thread.Sleep(1000 * r.Next(5)); Console.Write("{0}, ", i); } Console.WriteLine(); } } //第二種 //試圖鎖定的是一個實例級對象的私有方法,使用本身所在對象的引用就可以了 private void Show() { //使用當前對象作為線程標記 lock (this) { //所以在這范圍內的代碼是線程安全的 } } //第三種 //試圖鎖定一個靜態方法中的代碼,只需要聲明一個私有靜態對象成員變量作為鎖定標記就可以了 private static object staticLock = new object(); public static void showSt() { lock (staticLock) { //所以在這范圍內的代碼是線程安全的 } } } class Inter { private object c = new object(); int num = 0; public void Addone() { //lock (this) //{ // Thread.Sleep(3000); // num++; // Console.WriteLine(num); //} Thread.Sleep(3000); int newv = Interlocked.Increment(ref num); Console.WriteLine(newv); //int newv = Interlocked.Increment(ref num); } } }
並行開發學習資料:http://www.cnblogs.com/huangxincheng/archive/2012/04/02/2429543.html