C#學習筆記14。本站提示廣大學習愛好者:(C#學習筆記14)文章只能為提供參考,不一定能成為您想要的結果。以下是C#學習筆記14正文
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類所不支持的)。
6.WaitHandle類:多個同步類是承繼於它,如Mutex、EventWaitHandle、Semaphore,WaitHandle類的關鍵辦法為WaitOne(),它有多個重載版本,這些辦法會阻塞以後線程,直到WaitHandle實例收到信號或被設置(調用Set())。
7.重置事情類:重置事情與C#中委托以及事情沒有任何關系,用於多線程的控制,重置事情用於強迫代碼等候另一個線程的執行,直到取得事情已發作的告訴。重置事情類有ManualResetEvent、ManualResetEventSlim(.net4.0新增,針對前者停止優化)、AutoResetEvent(次要運用後面2個類型),它們提供的關鍵辦法為Set()與Wait()。調用Wait()辦法會阻塞一個線程的執行,直到一個不同的線程調用Set(),或許設定的等候時間完畢,才會持續運轉。可檢查Utility.UseManualResetEvent()代碼清單。
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); } }View Code
---------------------以上內容依據《C#實質論 第三版》停止整理