本示例主要學習如果對線程池中的操作實現超時,併在線程池中正確等待。 線程池還有一個ThreadPool.RegisterWaitForSingleObject,這個方法允許我們將回調函數放入線程池中的隊列中。當提供的等待事件處理器接收到信號或發生超時時,這個回調函數將被調用,這樣就實現了... ...
五、 線上程池中使用等待事件處理器與超時
本示例主要學習如果對線程池中的操作實現超時,併在線程池中正確等待。
線程池還有一個ThreadPool.RegisterWaitForSingleObject,這個方法允許我們將回調函數放入線程池中的隊列中。當提供的等待事件處理器接收到信號或發生超時時,這個回調函數將被調用,這樣就實現了為線程池中操作實現超時操作。
1.代碼如下:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace ThreadTPLDemo { class Program { static void Main(string[] args) { Console.WriteLine("開始測試線程池中定時運行操作。。。"); TimesOperation(TimeSpan.FromSeconds(5));//提供了5秒的操作時間,會超時 TimesOperation(TimeSpan.FromSeconds(9));//提供了9秒的操作時間,正常工作 Console.WriteLine("。。。。。。。。。。。。。。。。"); Console.Read(); } private static void TimesOperation(TimeSpan workTimes) { using (var manuEvt=new ManualResetEvent(false)) { using (var cts=new CancellationTokenSource()) { Console.WriteLine("開始--線程池中的定時操作。。。"); var work = ThreadPool.RegisterWaitForSingleObject (manuEvt, (state, isTimeOut) => AsyncOperWait(cts, isTimeOut), null, workTimes, true); Console.WriteLine("一個長時間運行的線程操作。。。"); ThreadPool.QueueUserWorkItem(_ => AsyncOper(cts.Token, manuEvt)); Console.WriteLine("。。。間隔2秒再次運行。。。"); Thread.Sleep(workTimes.Add(TimeSpan.FromSeconds(2))); work.Unregister(manuEvt); } } } private static void AsyncOper(CancellationToken token,ManualResetEvent mrevt) { Console.WriteLine("開始--線程池中的第一個工作線程。。。"); for (int i = 0; i < 7; i++) { if (token.IsCancellationRequested)//判斷是否已經取消操作 { Console.WriteLine("使用輪詢方法取消工作線程 ID:{0}", Thread.CurrentThread.ManagedThreadId); return; } Thread.Sleep(TimeSpan.FromSeconds(1)); } mrevt.Set(); Console.WriteLine("-------線程池中的第一個工作線程 發出信號----------"); } private static void AsyncOperWait(CancellationTokenSource cts, bool isTimeOut) { Console.WriteLine("開始--線程池中的第二個工作線程。。。"); if (isTimeOut)//判斷是否已經取消操作 { cts.Cancel(); Console.WriteLine("工作線程已經超時,並取消。 ID:{0}", Thread.CurrentThread.ManagedThreadId); } else { Console.WriteLine("-------線程池中的第二個工作線程 工作完成----------"); } } } }
2.程式結果如下。
程式啟動之後按順序放入了一些長時間運行的操作,這個操作運行6秒,如果運行成功,則會設置一個ManualResetEvent信號。如果取消了這個操作,則這個操作會被丟棄。
我們還註冊了第二個非同步操作,當從ManualResetEvent對象中接受了一個信號之後,這個非同步操作會被調用。如果第一個操作順利執行,則會設置信號。如果第一個操作執行超時,則會通過CancellationToken來取消第一個操作。
註:當線程池中大量的操作被阻塞時,上面的方法就非常有用了。
六、 使用計時器
使用Threading.Timer對象實現線程池中的周期性調用的非同步操作。
1.代碼如下:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace ThreadPoolDemo { class Program { static Timer timer; static void Main(string[] args) { Console.WriteLine("開始測試線程池中通過計時器運行操作,輸入A停止計時器。。。"); DateTime startTime = DateTime.Now; timer = new Timer(_=>TimesOperation(startTime),null,TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2)); Thread.Sleep(6000); timer.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4)); Console.WriteLine("。。。。。。。。。。。。。。。。"); ConsoleKeyInfo key = Console.ReadKey(); if (key.Key==ConsoleKey.A) { timer.Dispose(); } Console.Read(); } private static void TimesOperation(DateTime startTime) { TimeSpan time = DateTime.Now - startTime; Console.WriteLine("線程 {0} 從 {1} 開始 運行了 {2} 秒", Thread.CurrentThread.ManagedThreadId, startTime.ToString("yyyy-MM-dd HH:mm:ss"), time.Seconds); } } }
2.程式運行結果如下。
程式啟動時,首先創建了一個timer,第一個參數是lambla表達式,將會線上程池中執行,第二個參數是null。然後調用TimerOperation方法,並給一個初始時間,並指定什麼時候會第一次運行TimerOperation,以及之後的再次調用間隔時間。
七、 使用BackgroundWorker組件
本示例使用BackgroundWorker組件實現非同步操作。
程式啟動時創建了一個BackgroundWorker對象實例,顯示的指示這個後臺工作線程支持取消操作及操作進度通知。
1.代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; namespace ThreadTPLDemo { class Program { static Timer timer; static void Main(string[] args) { Console.WriteLine("開始測試 BackgroundWorker。。。"); BackgroundWorker bgwork = new BackgroundWorker(); bgwork.WorkerReportsProgress = true; bgwork.WorkerSupportsCancellation = true; bgwork.DoWork += worker_dowork; bgwork.ProgressChanged += worker_ProgressChanged; bgwork.RunWorkerCompleted += worker_Completed; bgwork.RunWorkerAsync();//開始後臺運行 Console.WriteLine("。。。。。輸入C取消BackgroundWorker後臺組件。。。。。。。。。。。"); do { ConsoleKeyInfo key = Console.ReadKey(); if (key.KeyChar.ToString().ToUpper() == "C") { bgwork.CancelAsync(); } } while (bgwork.IsBusy); Console.Read(); } static void worker_dowork(object sender,DoWorkEventArgs e) { Console.WriteLine("線程 {0} 開始 運行", Thread.CurrentThread.ManagedThreadId); int result = 0; var bgwork = (BackgroundWorker)sender; for (int i = 0; i < 100; i++) { if (bgwork.CancellationPending)//已經取消後臺操作 { e.Cancel = true; return; } if (i%10==0) { bgwork.ReportProgress(i);//顯示進度 } Thread.Sleep(200); result += i; } e.Result = result; } static void worker_ProgressChanged(object sender,ProgressChangedEventArgs e) { Console.WriteLine("線程 {0} 已經完成工作量的 {1} %", Thread.CurrentThread.ManagedThreadId,e.ProgressPercentage); } static void worker_Completed(object sender,RunWorkerCompletedEventArgs e) { Console.WriteLine("線程 {0} 已經執行結束!", Thread.CurrentThread.ManagedThreadId); if (e.Error!=null) { Console.WriteLine("線程 {0} 發生錯誤,錯誤信息:{1}", Thread.CurrentThread.ManagedThreadId,e.Error.Message); } else if (e.Cancelled) { Console.WriteLine("線程 {0} 已經取消", Thread.CurrentThread.ManagedThreadId); } else { Console.WriteLine("線程 {0} 執行成功,結果是:{1}", Thread.CurrentThread.ManagedThreadId, e.Result); } } } }
2.程式正常執行結束,如下圖。
3. 程式執行的中途,人工干預,取消。如下圖。請看下圖中黃框的位置,我輸入了字母C,則線程被取消。
在程式中我們定義了三個事件。
- DoWork事件,當一個後臺工作對象通過RunWorkerAsync啟動一個非同步操作時,將調用這個事件處理器。這個事件處理器會運行線上程池中,當運行結束,將運行結果做為參數傳遞給RunWorkerCompleted事件,同時觸發此事件。
- ProgressChanged事件,通過接收BackgroundWorker的ReportProgress方法傳遞過來的參數,顯示線程執行的進度。
- RunWorkerCompleted事件,在此事件中可以知道操作是成功完成,還是發生了錯誤,或是被取消。