EAP提供了一個簡單的方式來使用多線程編程,提供了多線程能力的同時而不用顯式的打開或者管理線程。同時提供了一個取消模型,可以安全的更新WPF和WindowsFormsControl的屬性。
EAP只是一個模型,這些特性必須要采用實際的代碼來實現。在.NET Framework中最明顯的就是BackgroundWorker類、WebClient類等。這些類中的方法以*Async結尾的犯法就是異步執行的:它們新打開一個線程,將結果返回給調用的方法,當執行完畢之後調用以*Completed結尾的方法來處理,這個方法會自動調用Invoke方法(WPF和WindowForms中)。
這是一個System.ComponentModel命名空間中的輔助類,這是一個一般意義上EAP模型的實現。這個類使用了線程池,因此不能調用Abort方法。
static BackgroundWorker _bw = new BackgroundWorker(); static void Main() { _bw.DoWork += bw_DoWork; _bw.RunWorkerAsync ("Message to worker"); Console.ReadLine(); } static void bw_DoWork (object sender, DoWorkEventArgs e) { // This is called on the worker thread Console.WriteLine (e.Argument); // writes "Message to worker" // Perform time-consuming task... }RunWorkerAsync方法將參數總是傳遞給DoWork方法。同時在DoWork方法執行完畢之後,有一個RunWorkerCompleted事件,來處理執行完畢之後的動作。 支持進度報告:需要設置
WorkerReportsProgress
屬性為true,同時周期性的在DoWork中調用ReportProgress
方法,然後還要處理ProgressChanged
事件,通過它的事件參數的屬性ProgressPercentage來實現。ProgressChanged事件中的代碼可以自由的與UI控件進行交互,對更新進度條尤為有用。支持退出報告:設置WorkerSupportsCancellation屬性為true;在DoWork中周期地檢查CancellationPending屬性:如果為true,就設置事件參數的Cancel屬性為true,然後返回。(工作線程可能會設置Cancel為true,並且不通過CancellationPending進行提示——如果判定工作太過困難並且它不能繼續運行);調用CancelAsync來請求退出
周期性的執行某個方法最簡單的方法就是使用一個計時器,比如System.Threading 命名空間下Timer類。線程計時器利用了線程池,允許多個計時器被創建而沒有額外的線程開銷。 Timer 算是相當簡易的類,它有一個構造器和兩個方法(這對於極簡主義者來說是最高興不過的了)。
public sealed class Timer : MarshalByRefObject, IDisposable { public Timer (TimerCallback tick, object state, 1st, subsequent); public bool Change (1st, subsequent); // To change the interval public void Dispose(); // To kill the timer } 1st = time to the first tick in milliseconds or a TimeSpan subsequent = subsequent intervals in milliseconds or a TimeSpan (use Timeout.Infinite for a one-off callback)
接下來這個例子,計時器5秒鐘之後調用了Tick 的方法,它寫"tick...",然後每秒寫一個,直到用戶敲 Enter:
using System; using System.Threading; class Program { static void Main() { Timer tmr = new Timer (Tick, "tick...", 5000, 1000); Console.ReadLine(); tmr.Dispose(); // End the timer } static void Tick (object data) { // This runs on a pooled thread Console.WriteLine (data); // Writes "tick..." } }
.NET framework在System.Timers命名空間下提供了另一個計時器類。它完全包裝自System.Threading.Timer,在使用相同的線程池時提供了額外的便利——相同的底層引擎。下面是增加的特性的摘要:
例子:
using System; using System.Timers; // Timers namespace rather than Threading class SystemTimer { static void Main() { Timer tmr = new Timer(); // Doesn't require any args tmr.Interval = 500; tmr.Elapsed += tmr_Elapsed; // Uses an event instead of a delegate tmr.Start(); // Start the timer Console.ReadLine(); tmr.Stop(); // Pause the timer Console.ReadLine(); tmr.Start(); // Resume the timer Console.ReadLine(); tmr.Dispose(); // Permanently stop the timer } static void tmr_Elapsed (object sender, EventArgs e) { Console.WriteLine ("Tick"); } }
.NET framework 還提供了第三個計時器——在System.Windows.Forms 命名空間下。雖然類似於System.Timers.Timer 的接口,但功能特性上有根本的不同。一個Windows Forms 計時器不能使用線程池,代替為總是在最初創建它的線程上觸發 "Tick"事件。假定這是主線程——負責實例化所有Windows Forms程序中的forms和控件,計時器的事件能夠操作forms和控件而不違反線程安全——或者強加單元線程模式。Control.Invoke是不需要的。它實質上是一個單線程timer
Windows Forms計時器必須迅速地執行來更新用戶接口。迅速地執行是非常重要的,因為Tick事件被主線程調用,如果它有停頓, 將使用戶接口變的沒有響應。
每個線程與其它線程數據存儲是隔離的,這對於“不相干的區域”的存儲是有益的,它支持執行路徑的基礎結構,如通信,事務和安全令牌。 通過方法參數傳遞這些數據是十分笨拙的。存儲這些數據到靜態域意味著這些數據可以被所有線程共享。
Thread.GetData從一個線程的隔離數據中讀,Thread.SetData 寫入數據。 兩個方法需要一個LocalDataStoreSlot對象來識別內存槽——這包裝自一個內存槽的名稱的字符串,這個名稱 你可以跨所有的線程使用,它們將得到不各自的值,看這個例子:
class ... { // 相同的LocalDataStoreSlot 對象可以用於跨所有線程 LocalDataStoreSlot secSlot = Thread.GetNamedDataSlot ("securityLevel"); // 這個屬性每個線程有不同的值 int SecurityLevel { get { object data = Thread.GetData (secSlot); return data == null ? 0 : (int) data; // null == 未初始化 } set { Thread.SetData (secSlot, value); } }
Thread.FreeNamedDataSlot將釋放給定的數據槽,它跨所有的線程——但只有一次,當所有相同名字LocalDataStoreSlot對象作為垃圾被回收時退出作用域時發生。這確保了線程不得到數據槽從它們的腳底下撤出——也保持了引用適當的使用之中的LocalDataStoreSlot對象。