1.在多個線程的同步數據中,避免使用this、typeof(type)、string進行同步鎖,使用這3個容易形成死鎖。編程
2.使用Interlocked類:咱們通常使用的互斥鎖定模式(同步數據)爲Lock關鍵字(即Monitor類),這個同步屬於代價很是高的一種操做。除了使用Monitor以外,還有一個備選方案,它一般直接由處理器支持,並且面向特定的同步模式。Interlocked類中包含一些經常使用方法,如CompareExchange、Decrement、Increment、Exchange。這些都是針對單個的值(對象)進行同步數據處理。安全
3.多個線程時的事件通知:能夠查看Utility.EventInThread()代碼清單。服務器
4.同步設計的最佳實踐:多線程
(1)避免死鎖;如兩個線程都等待對方鎖定的資源釋放,線程A鎖定sync1資源,線程B鎖定sync2資源,線程A請求鎖定sync2資源,線程B請求鎖定sync1資源,此時便出現死鎖。死鎖的發生必須知足4個基本條件。互斥、佔有並等待、不可搶先、循環等待條件。併發
(2)什麼時候提供同步;一般針對靜態數據進行同步,並有公共方法來修改數據,方法內部應處理好同步問題。框架
(3)避免沒必要要的鎖定。異步
5.更多同步類型:System.Threading.Mutex類在概念上與Monitor類一致,只是其是爲支持進程之間的同步。如同步對文件或其餘跨進程資源的訪問,限制程序只能運行一個實例。如Utility.UseMutex()代碼清單。Mutex類派生自WaitHandle,能夠自動獲取多個鎖(這是Monitor類所不支持的)。async
6.WaitHandle類:多個同步類是繼承於它,如Mutex、EventWaitHandle、Semaphore,WaitHandle類的關鍵方法爲WaitOne(),它有多個重載版本,這些方法會阻塞當前線程,直到WaitHandle實例收到信號或被設置(調用Set())。分佈式
7.重置事件類:重置事件與C#中委託以及事件沒有任何關係,用於多線程的控制,重置事件用於強迫代碼等候另外一個線程的執行,直到得到事件已發生的通知。重置事件類有ManualResetEvent、ManualResetEventSlim(.net4.0新增,針對前者進行優化)、AutoResetEvent(主要使用前面2個類型),它們提供的關鍵方法爲Set()與Wait()。調用Wait()方法會阻塞一個線程的執行,直到一個不一樣的線程調用Set(),或者設定的等待時間結束,纔會繼續運行。可查看Utility.UseManualResetEvent()代碼清單。ide
8.併發集合類:.net4.0新增了一些類是併發集合類,這些類專門設計用來包含內建的同步代碼,使它們能支持多個線程訪問而沒必要關心競態條件。如BlockingCollection<T>、ConcurrentBag<T>、ConcurrentDictionary<K,V>、ConcurrentQueue<T>、ConcurrentStack<T>,利用併發集合,能夠實現的一個常見的模式是生產者和消費者的線程安全的訪問。
9.線程本地存儲:同步的一個替代方案是隔離,而實現隔離的一個辦法是使用線程本地存儲,利用線程本地存儲,線程就有了專屬的變量實例。線程本地存儲實現有2中方式,分別爲ThreadLocal<T>和ThreadStaticAttribute類,其中ThreadLocal<T>類是.net4.0新增的。可查看LocalVarThread類的代碼清單。
10.計時器:有三種計時器分別爲System.Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer,Forms.Timer用於用戶界面編程,可以安全的訪問用戶界面上的窗體與控件,Timers.Timer是Threading.Timer的包裝器,是對其功能的抽象(System.Threading.Timer類型輕量一些)。
功能描述 |
System.Timers.Timer |
System.Threading.Timer |
System.Window.Forms.Timer |
支持在計時器實例化以後添加和刪除偵聽器 |
是 |
否 |
是 |
支持用戶界面線程上的回調 |
是 |
否 |
是 |
從自線程池獲取的線程進行回調 |
是 |
是 |
否 |
支持在Windows窗體設計器中拖放 |
是 |
否 |
是 |
適合在一個多線程服務器環境中運行 |
是 |
是 |
否 |
支持將任意狀態從計時器初始化傳遞至回調 |
否 |
是 |
否 |
實現Idisposable |
是 |
是 |
是 |
支持開關式回調和按期重複回調 |
是 |
是 |
是 |
可穿越應用程序域的邊界訪問 |
是 |
是 |
是 |
支持Icomponent,可容納在一個Icontainer中 |
是 |
否 |
是 |
11.異步編程模型(Async Program Model,APM):異步編程是多線程的一種方式,APM的關鍵在於成對使用BeginX和EndX方法(X通常對應同步版本的方法名),並且這些方法具備完善的簽名。BeginX返回一個System.IAsyncResult對象,可經過它訪問異步調用的狀態,以便等待完成或輪詢完成。然而EndX方法獲取這個返回的對象做爲輸入參數。這樣才真正將兩個方法配成一對,讓咱們能夠清晰地判斷哪一個BeginX方法調用和哪一個EndX方法調用配對。APM的本質要求全部BeginX調用都必須有一個(並且只能有一個)EndX調用。所以,不可能發生兩個EndX調用接受同一個IAsyncResult實例的狀況。咱們還可使用IAsyncResult的WaitHandle判斷異步方法什麼時候結束,IAsyncResult的WaitHandle是在回調執行以前進行通知完成。
EndX方法具備4個方面的用途。
首先,調用EndX會阻塞線程繼續執行,直到請求的工做成功完成(或者發生錯誤並引起異常)。
其次,若是方法X要返回數據,這個數據可從EndX方法調用中訪問。
再次,若是執行請求的工做時發生異常,可在調用EndX時從新引起這個異常,確保異常會被調用代碼發現——好像它是在一次同步調用上發生的那樣。
最後,若是任何資源須要在調用X後清理,EndX將負責清理這些資源。
BeginX方法有兩個額外的參數,在同步版本的方法中是沒有的,一個是回調參數,是方法結束時要調用的一個System.AsyncCallback委託,另外一個是object類型的狀態參數(State)。在使用回調時,能夠把EndX放在其內部執行。
12.使用TPL(任務並行庫)調用APM:雖然TPL大幅簡化了長時間運行方法的異步調用,但一般最好是使用API提供的APM方法,而不是針對同步版本編寫TPL。這是由於API開發人員知道如何編寫最高效率的線程處理代碼,知道同步哪些數據以及要使用什麼同步類型。TPL包含FromAsync的一組重載版本,用於調用APM。
13.異步委託調用:有一個派生的APM模式,稱爲異步委託調用,它在全部委託數據類型上使用了特殊的、由C#編譯器生成的代碼。例如,給定Func<string,int>的一個委託實例,能夠在這個實例上使用如下APM方法對。
System.IAsyncResult BeginInvoke(string arg,AsyncCallback callback,object obj)
Int EndInvode(IasyncResult result)
結果是可使用C#編譯器生成的方法來同步地調用任何委託(進而調用任何方法)。遺憾的是,異步委託調用模式使用的基礎技術是一種再也不繼續開發的分佈式編程技術,稱爲遠程處理。雖然微軟仍然支持異步委託調用,並且在能夠預見的未來,也不會放棄對它的支持,但和其餘技術相比,它的性能顯得比較通常。其餘技術包括Thread、ThreadPool和TPL等。所以,在開發新項目時,開發人員應儘可能選用其餘技術,而不要使用異步委託調用API。在TPL以前,異步委託調用模式比其餘替代方案容易得多,因此假如一個API沒有提供顯式的異步調用模式,通常都會選用它。然而,在TPL問世以後,除非是爲了與.Net3.5和早期框架版本兼容,不然異步委託調用愈來愈沒有什麼用了。
14.基於事件的異步模式(EAP):比APM更高級的一種編程模式是基於事件的異步模式。和APM同樣,API開發人員爲長時間運行的方法實現了EAP。其中Background Worker模式,它是EAP的一個特定的實現。
15.Background Worker模式:創建Background Worker模式的過程以下。
(1)爲BackgroundWorker.DoWork事件註冊長時間運行的方法。
(2)爲了接受進展或狀態通知,要爲BackgroundWorker.ProgressChanged掛接一個偵聽器,並將BackgroundWorker.WorkerReportsProgress設爲true。
(3)爲BackgroundWorker.RunWorkerCompleted事件註冊一個方法。
(4)爲WorkerSupportsCancellation屬性賦值以支持取消一個操做。將true值賦給該屬性之後,對BackgroundWorker.CancelAsync的調用就會設置DoWorkEventArgs.CancellationPending標誌。
(5)在DoWork提供的方法內,檢查DoWorkEventArgs.CancellationPending屬性值,並在它爲true時退出方法。
(6)一切都設置好以後,調用BackgroundWorker.RunWorkerAsync(),並提供要傳給指定DoWork()方法的一個狀態參數來開始工做。
分解成以上小步驟之後,Background Worker模式就顯得容易理解。另外,因爲它本質上是一種EAP,因此提供了對進度通知的顯式支持。後臺的工做者(worker)線程異步執行的時候,假如發生一個未處理的異常,RunWorkerCompleted委託的RunWorkerCompletedEventArgs.Error屬性就會設置成Exception實例。所以,咱們經過在RunWorkerCompleted回調內檢查Error屬性來提供異常處理機制。
16.Windows UI編程:使用System.Windows.Forms和System.Windows命名空間來進行用戶界面開發時,也必須注意線程處理問題。Microsoft Windows系列操做系統使用的是一個單線程的、基於消息處理的用戶界面。這意味着,每次只能有一個線程訪問用戶界面,與輪換線程的任何交互都應該經過Windows消息泵來封送。
17.Windows窗體:進行Windows窗體編程時,爲了檢查是否容許從一個線程中發出UI調用,須要調用一個組件的InvokeRequired屬性,判斷是否須要進行封送處理。若是InvokeRequired返回true,代表須要封送,並可經過一個Invoke()調用來實現。儘管Invoke()在內部不管如何都會檢查InvokeRequired,但更有效率的作法是提早顯示地檢查這個屬性。因爲封送到另外一個線程多是至關慢的一個過程,因此能夠經過BeginInvoke()和EndInvoke()來進行異步調用。Invoke()、BeginInvoke()、EndInvoke()和InvokeRequired構成了System.ComponentModel.ISynchronizeInvoke接口的成員。該接口已由System.Windows.Forms.Control實現,全部Windows窗體控件都是從這個Control類派生。
18.Windows Presentation Foundation(WPF):爲了在WPF平臺上實現相同的封送檢查,須要採起稍有不一樣的一種方式。WPF包含一個名爲Current的靜態成員屬性,它的類型是DispatcherObject,由System.Windows.Application類提供。在調度器(dispatcher)對象上調用CheckAccess(),做用等同於在Windows窗體中的控件上調用InvokeRequired,而後再使用Application.Current.Dispatcher.Invoke()方法封送。
19.說明:除了TPL提供的模式,還有這麼多額外的模式可供選用,這形成許多人不知道應該如何選擇。通常狀況下,最好是選擇由API提供的模式(好比APM或EAP),最後選擇TPL模式。
public class Utility { private static ManualResetEventSlim firstEvent, secendEvent; private static object _data; public static void Initialize(object newValue) { Interlocked.CompareExchange(ref _data, newValue, null); } public static void EventInThread() { //不是線程安全,在檢查委託對象與調用委託之間,存在其餘線程對OnTemparatureChange進行賦值操做,可能會被設置爲null。 /*if (OnTemparatureChange != null) { //調用訂閱者 OnTemparatureChange(this, new TemparatureEventArgs()); }*/ //線程安全操做,建立一個委託變量副本,檢查副本的null,再觸發副本。這樣即便OnTemparatureChange委託變量在其餘線程中被null化,也不影響。 /*TemparatureChangedHandle localChanged = OnTemparatureChange; if (localChanged != null) { //調用訂閱者 localChanged(this, new TemparatureEventArgs()); }*/ } public static void UseMutex() { bool firstApplicationInstance; string mutexName = Assembly.GetEntryAssembly().FullName; using (Mutex mutex = new Mutex(false, mutexName, out firstApplicationInstance)) { if (!firstApplicationInstance) { Console.WriteLine("This Application is already running."); } else { Console.WriteLine("Enter to shutdown."); Console.ReadLine(); } } } public static void UseManualResetEvent() { using (firstEvent = new ManualResetEventSlim()) using (secendEvent = new ManualResetEventSlim()) { Console.WriteLine("App start"); Console.WriteLine("start task"); Task task = Task.Factory.StartNew(() => { Console.WriteLine("DoWork start"); Thread.Sleep(1000); firstEvent.Set(); secendEvent.Wait(); Console.WriteLine("DoWork end"); }); firstEvent.Wait(); Console.WriteLine("Thread executing"); secendEvent.Set(); task.Wait(); Console.WriteLine("Thread completed"); Console.WriteLine("App shutdown"); } } } public class LocalVarThread { public static ThreadLocal<double> _count = new ThreadLocal<double>(() => 0.01134); public static double Count { set { _count.Value = value; } get { return _count.Value; } } public static void DoWork() { Task.Factory.StartNew(Decrement); for (int i = 0; i < short.MaxValue; i++) { Count++; } Console.WriteLine("DoWork Count = {0}", Count); } public static void Decrement() { Count = -Count; for (int i = 0; i < short.MaxValue; i++) { Count--; } Console.WriteLine("Decrement Count = {0}", Count); } }
---------------------以上內容根據《C#本質論 第三版》進行整理