Net66編寫
一、引言
在編寫Windows form時,如果直接在UI線程要運行一個費時方法的話(如從數據庫查詢大量數據時),會引起程序“假死”,從而導致用戶不滿。這個時候就需要通過多線程技術來解決,提高界面交互性能,方便用戶使用。
一般通過三種方式解決:
1.通過System.Threading.Thread類,創建新的線程,Thread.Start運行費時方法。
2.通過System.Threading.ThreadPool類,將費時任務提交到線程池中,等待運行。
以上兩種方法,基本思路是在UI界面中控制線程的啓動和中止,在線程中回調用UI界面方法,更新界面。在線程中回調UI界面方法時,特別是涉及更新控件屬性時,如果不注意,存在很大的隱患。這兩種辦法,編碼和控制結構較爲複雜,需要啓動和管理額外的線程佔用資源。
3.通過異步委託調用,將該方法排隊到系統線程池的線程中運行,而在費時方法中也通過Control.BeginInvoke異步回調,達到"啓動後不管"的目的。
這種方法,編碼簡單,程序結構較爲清晰,充分利用.NET框架的異步委託功能,但要對異步調用知識較熟悉。
相關知識點參見
現利用.NET異步委託調用功能,編寫Task抽象類,以方便管理後臺工作線程,銜接後臺線程與UI線程的聯繫。該抽象類提供了調用和管理的框架,沒有方法的實現細節,通過繼承類、重寫方法,可以實現想要的功能。主要功能如下:
1.利用異步委託調用,實際多線程,不需要單獨後臺線程。
2.通過委託、事件驅動,實際後臺與前臺UI線程的聯繫,實現事件廣播。
3.支持正常取消後臺工作方法(費時方法)運行,也可以強制中止線程。
4.能夠捕獲取消、強制中止和方法出錯三種情況,並突發相關事件,以便進行釋放資源等操作。
5.通過異步調用,在工作方法中安全調用涉及UI控件的方法。
6.自行管理工作進程狀態,提供狀態變化事件。
7.只要工作方法調用簽名,符合定義的TaskDelegate委託接口,可通過StartTask(TaskDelegate worker ,params object[] args )方便調用。在實際使用時,可在繼承類中定義多個相同調用接口的方法,避免重複編碼,較爲方便。
給大家作個參考,而大牛呢,多點指正。當是扔個磚頭,想砸塊玉吧。
二、代碼
2using System.Windows.Forms;
3
4namespace Net66.AsynchThread
5{
6 /**//// <summary>
7 /// 任務工作狀態
8 /// </summary>
9 public enum TaskStatus
10 {
11 /**//// <summary>
12 /// 任務沒有運行,可能是工作進程沒有開始、工作進程正常結束或正常取消工作進程
13 /// </summary>
14 Stopped,
15 /**//// <summary>
16 /// 任務沒有運行,被調用者強行中止
17 /// </summary>
18 Aborted,
19 /**//// <summary>
20 /// 任務沒有運行,在工作進程中觸發錯誤而中止
21 /// </summary>
22 ThrowErrorStoped,
23 /**//// <summary>
24 /// 任務運行中
25 /// </summary>
26 Running,
27 /**//// <summary>
28 /// 嘗試取消工作進程中
29 /// </summary>
30 CancelPending,
31 /**//// <summary>
32 /// 強行中止工作進程中
33 /// </summary>
34 AbortPending
35
36 }
37
38 /**//// <summary>
39 /// 任務狀態消息
40 /// </summary>
41 public class TaskEventArgs : EventArgs
42 {
43 /**//// <summary>
44 /// 任務運行結果
45 /// </summary>
46 public Object Result;
47 /**//// <summary>
48 /// 任務進度(0-100)
49 /// </summary>
50 public int Progress;
51 /**//// <summary>
52 /// 任務工作狀態
53 /// </summary>
54 public TaskStatus Status;
55 /**//// <summary>
56 /// 任務消息文本
57 /// </summary>
58 public String Message;
59 /**//// <summary>
60 /// 創建任務狀態消息
61 /// </summary>
62 /// <param name="progress">任務進度(0-100)</param>
63 public TaskEventArgs( int progress )
64 {
65 this.Progress = progress;
66 this.Status = TaskStatus.Running;
67 }
68 /**//// <summary>
69 /// 創建任務狀態消息
70 /// </summary>
71 /// <param name="status">任務線程狀態</param>
72 public TaskEventArgs( TaskStatus status )
73 {
74 this.Status = status;
75 }
76 /**//// <summary>
77 /// 創建任務狀態消息
78 /// </summary>
79 /// <param name="progress">任務進度(0-100)</param>
80 /// <param name="result">任務運行中間結果</param>
81 public TaskEventArgs( int progress,object result )
82 {
83 this.Progress = progress;
84 this.Status = TaskStatus.Running;
85 this.Result = result;
86 }
87 /**//// <summary>
88 /// 創建任務狀態消息
89 /// </summary>
90 /// <param name="status">任務線程狀態</param>
91 /// <param name="result">任務運行結果</param>
92 public TaskEventArgs( TaskStatus status,object result )
93 {
94 this.Status = status;
95 this.Result = result;
96 }
97 /**//// <summary>
98 /// 創建任務狀態消息
99 /// </summary>
100 /// <param name="status">任務線程狀態</param>
101 /// <param name="message">消息文本</param>
102 /// <param name="result">任務運行結果</param>
103 public TaskEventArgs( TaskStatus status,string message ,object result )
104 {
105 this.Status = status;
106 this.Message = message;
107 this.Result = result;
108 }
109 /**//// <summary>
110 /// 創建任務狀態消息
111 /// </summary>
112 /// <param name="progress">任務進度(0-100)</param>
113 /// <param name="message">消息文本</param>
114 /// <param name="result">任務運行中間結果</param>
115 public TaskEventArgs( int progress,string message ,object result )
116 {
117 this.Progress = progress;
118 this.Status = TaskStatus.Running;
119 this.Message = message;
120 this.Result = result;
121 }
122 /**//// <summary>
123 /// 創建任務狀態消息
124 /// </summary>
125 /// <param name="status">任務線程狀態</param>
126 /// <param name="progress">任務進度(0-100)</param>
127 /// <param name="message">消息文本</param>
128 /// <param name="result">任務運行中間結果</param>
129 public TaskEventArgs( TaskStatus status,int progress,string message ,object result )
130 {
131 this.Status = status;
132 this.Progress = progress;
133 this.Message = message;
134 this.Result = result;
135 }
136 }
137
138 /**//// <summary>
139 /// 任務的工作方法(Work)的委託接口
140 /// 傳入值:對象數組(object[])
141 /// 返回值:對象(object)
142 /// </summary>
143 public delegate object TaskDelegate( params object[] args );
144
145 /**//// <summary>
146 /// 任務事件的委託接口
147 /// </summary>
148 public delegate void TaskEventHandler( object sender, TaskEventArgs e );
149
150 abstract public class Task
151 {
152 內部屬性#region 內部屬性
153 /**//// <summary>
154 /// 任務調用線程(前臺或UI線程)
155 /// </summary>
156 protected System.Threading.Thread _callThread = null;
157 /**//// <summary>
158 /// 任務工作線程(後臺)
159 /// </summary>
160 protected System.Threading.Thread _workThread = null;
161 /**//// <summary>
162 /// 任務工作狀態
163 /// </summary>
164 protected TaskStatus _taskState = TaskStatus.Stopped;
165 /**//// <summary>
166 /// 任務進度(0-100)
167 /// </summary>
168 protected int _progress = -1;
169 /**//// <summary>
170 /// 任務工作結果
171 /// </summary>
172 protected object _result = null;
173 /**//// <summary>
174 /// 任務工作進程出錯時,捕獲的異常對象
175 /// </summary>
176 protected Exception _exception = null;
177 #endregion
178
179 事件#region 事件
180 /**//// <summary>
181 /// 任務工作狀態變化事件
182 /// </summary>
183 public event TaskEventHandler TaskStatusChanged;
184 /**//// <summary>
185 /// 任務進度變化事件
186 /// </summary>
187 public event TaskEventHandler TaskProgressChanged;
188 /**//// <summary>
189 /// 任務被調用者強行中止事件
190 /// </summary>
191 public event TaskEventHandler TaskAbort;
192 /**//// <summary>
193 /// 任務工作方法執行中觸發錯誤事件
194 /// </summary>
195 public event TaskEventHandler TaskThrowError;
196 /**//// <summary>
197 /// 任務被調用者取消事件
198 /// </summary>
199 public event TaskEventHandler TaskCancel;
200 #endregion
201
202 屬性#region 屬性
203
204 /**//// <summary>
205 /// 任務工作進程出錯時,捕獲的異常對象
206 /// </summary>
207 public Exception Exception
208 {
209 get { return _exception;}
210 }
211 /**//// <summary>
212 /// 任務調用線程(前臺或UI線程)
213 /// </summary>
214 public System.Threading.Thread CallThread
215 {
216 get { return _callThread;}
217 }
218 /**//// <summary>
219 /// 任務工作線程(後臺)
220 /// </summary>
221 public System.Threading.Thread WordThread
222 {
223 get {return _workThread;}
224 }
225
226 /**//// <summary>
227 /// 任務進度(0-100)
228 /// </summary>
229 public int Progress
230 {
231 get {return _progress;}
232 }
233 /**//// <summary>
234 /// 任務工作狀態
235 /// </summary>
236 public TaskStatus TaskState
237 {
238 get {return _taskState;}
239 }
240 /**//// <summary>
241 /// 任務工作結果
242 /// </summary>
243 public object Result
244 {
245 get {return _result;}
246 }
247
248 protected bool IsStop
249 {
250 get
251 {
252 bool result = false;
253 switch (_taskState)
254 {
255 case TaskStatus.Stopped:
256 case TaskStatus.Aborted:
257 case TaskStatus.ThrowErrorStoped:
258 result = true;
259 break;
260 default:
261 break;
262 }
263 return result;
264 }
265 }
266 #endregion
267
268 觸發事件#region 觸發事件
269 /**//// <summary>
270 /// 觸發任務工作狀態變化事件
271 /// </summary>
272 /// <param name="status">任務工作狀態</param>
273 /// <param name="result">任務工作結果對象</param>
274 protected void FireStatusChangedEvent(TaskStatus status, object result)
275 {
276 if( TaskStatusChanged != null )
277 {
278 TaskEventArgs args = new TaskEventArgs( status,result);
279 AsyncInvoke(TaskStatusChanged,args);
280 }
281 }
282
283 /**//// <summary>
284 /// 觸發任務進度變化事件
285 /// </summary>
286 /// <param name="progress">任務進度(0-100)</param>
287 /// <param name="result">任務工作中間結果對象</param>
288 protected void FireProgressChangedEvent(int progress, object result)
289 {
290 if( TaskProgressChanged != null )
291 {
292 TaskEventArgs args = new TaskEventArgs( progress,result);
293 AsyncInvoke(TaskProgressChanged,args);
294 }
295 }
296 /**//// <summary>
297 /// 觸發工作方法執行中發現錯誤事件
298 /// </summary>
299 /// <param name="progress">任務進度(0-100)</param>
300 /// <param name="result">任務工作中間結果對象</param>
301 protected void FireThrowErrorEvent(int progress, object result)
302 {
303 if( TaskThrowError != null )
304 {
305 TaskEventArgs args = new TaskEventArgs( progress,result);
306 AsyncInvoke(TaskThrowError,args);
307 }
308 }
309 /**//// <summary>
310 /// 觸發被調用者取消事件
311 /// </summary>
312 /// <param name="progress">任務進度(0-100)</param>
313 /// <param name="result">任務工作中間結果對象</param>
314 protected void FireCancelEvent(int progress, object result)
315 {
316 if( TaskCancel != null )
317 {
318 TaskEventArgs args = new TaskEventArgs( progress,result);
319 AsyncInvoke(TaskCancel,args);
320 }
321 }
322 /**//// <summary>
323 /// 觸發被調用者強行中止事件
324 /// </summary>
325 /// <param name="progress">任務進度(0-100)</param>
326 /// <param name="result">任務工作中間結果對象</param>
327 protected void FireAbortEvent(int progress, object result)
328 {
329 if( TaskAbort != null )
330 {
331 TaskEventArgs args = new TaskEventArgs( progress,result);
332 AsyncInvoke(TaskAbort,args);
333 }
334 }
335 /**//// <summary>
336 /// 異步調用掛接事件委託
337 /// </summary>
338 /// <param name="eventhandler">事件處理方法句柄</param>
339 /// <param name="args">事件消息</param>
340 protected void AsyncInvoke(TaskEventHandler eventhandler,TaskEventArgs args)
341 {
342// TaskEventHandler[] tpcs = (TaskEventHandler[])eventhandler.GetInvocationList();
343 Delegate[] tpcs = eventhandler.GetInvocationList();
344 foreach(TaskEventHandler tpc in tpcs)
345 {
346 if ( tpc.Target is System.Windows.Forms.Control )
347 {
348 Control targetForm = tpc.Target as System.Windows.Forms.Control;
349 targetForm.BeginInvoke( tpc,new object[] { this, args } );
350 }
351 else
352 {
353 tpc.BeginInvoke(this, args ,null,null); //異步調用,啓動後不管
354 }
355 }
356 }
357 #endregion
358
359 工作進程管理#region 工作進程管理
360 /**//// <summary>
361 /// 開啓任務默認的工作進程
362 /// [public object Work(params object[] args )]
363 /// </summary>
364 /// <param name="args">傳入的參數數組</param>
365 public bool StartTask( params object[] args )
366 {
367 return StartTask(new TaskDelegate( Work ),args);
368 }
369 /**//// <summary>
370 /// 開啓任務的工作進程
371 /// 將開啓符合TaskDelegate委託接口的worker工作方法
372 /// </summary>
373 /// <param name="worker">工作方法</param>
374 /// <param name="args">傳入的參數數組</param>
375 public bool StartTask(TaskDelegate worker ,params object[] args )
376 {
377 bool result =false;
378 lock( this )
379 {
380 if( IsStop && worker != null )
381 {
382 _result = null;
383 _callThread = System.Threading.Thread.CurrentThread;
384 // 開始工作方法進程,異步開啓,傳送回調方法
385 worker.BeginInvoke( args ,new AsyncCallback( EndWorkBack ), worker );
386 // 更新任務工作狀態
387 _taskState = TaskStatus.Running;
388 // 觸發任務工作狀態變化事件
389 FireStatusChangedEvent( _taskState, null);
390 result = true;
391 }
392 }
393 return result;
394 }
395 /**//// <summary>
396 /// 請求停止任務進程
397 /// 是否停止成功,應看任務工作狀態屬性TaskState是否爲TaskStatus.Stop
398 /// </summary>
399 public bool StopTask()
400 {
401 bool result =false;
402 lock( this )
403 {
404 if( _taskState == TaskStatus.Running )
405 {
406 // 更新任務工作狀態
407 _taskState = TaskStatus.CancelPending;
408 // 觸發任務工作狀態變化事件
409 FireStatusChangedEvent( _taskState, _result);
410 result = true;
411 }
412 }
413 return result;
414 }
415 /**//// <summary>
416 /// 強行中止任務的工作線程
417 ///
418 /// </summary>
419 public bool AbortTask()
420 {
421 bool result = false;
422 lock( this )
423 {
424 if( _taskState == TaskStatus.Running && _workThread != null )
425 {
426 if (_workThread.ThreadState != System.Threading.ThreadState.Stopped)
427 {
428 _workThread.Abort();
429 }
430 System.Threading.Thread.Sleep(2);
431 if (_workThread.ThreadState == System.Threading.ThreadState.Stopped)
432 {
433 // 更新任務工作狀態
434 _taskState = TaskStatus.Aborted;
435 result = true;
436 }
437 else
438 {
439 // 更新任務工作狀態
440 _taskState = TaskStatus.AbortPending;
441 result = false;
442 }
443 // 觸發任務工作狀態變化事件
444 FireStatusChangedEvent( _taskState, _result);
445 }
446 }
447 return result;
448 }
449
450 /**//// <summary>
451 /// 工作方法完成後的回調方法
452 /// 將檢查是否出錯,並獲取、更新返回結果值
453 /// </summary>
454 /// <param name="ar">異步調用信號對象</param>
455 protected void EndWorkBack( IAsyncResult ar )
456 {
457 bool error = false;
458 bool abort = false;
459 try //檢查是否錯誤
460 {
461 TaskDelegate del = (TaskDelegate)ar.AsyncState;
462 _result = del.EndInvoke( ar );
463 }
464 catch(Exception e) //如果錯誤,則保存錯誤對象
465 {
466 error = true;
467 _exception = e;
468 if (e.GetType() == typeof(System.Threading.ThreadAbortException))
469 {
470 abort = true;
471 FireAbortEvent(_progress,_exception);
472 }
473 else
474 {
475 FireThrowErrorEvent(_progress,_exception);
476 }
477 }
478 lock( this )
479 {
480 if (error)
481 {
482 if ( abort)
483 {
484 _taskState = TaskStatus.Aborted; //調用者強行中止
485 }
486 else
487 {
488 _taskState = TaskStatus.ThrowErrorStoped;//出現錯誤而中止
489 }
490 }
491 else
492 { _taskState = TaskStatus.Stopped;} //正常結束
493 FireStatusChangedEvent( _taskState, _result);
494 }
495 }
496 #endregion
497
498 工作方法的基礎#region 工作方法的基礎
499 /**//// <summary>
500 /// 工作方法
501 /// 在繼承類中應重寫(override)此方法,以實現具體的工作內容,注意以幾點:
502 /// 1.須在繼承類是引用base.Work,在基類(base)的Work方法中,執行線程設爲IsBackground=true,並保存工作線程對象
503 /// 2.在繼承類中,應及時更新_progress與_result對象,以使Progress和Result屬性值正確
504 /// 3.在執行過程中應檢查_taskState,以使任務中被請求停止後(_taskState爲TaskStatus.CancelPending),工作線程能最快終止.
505 /// 4.如在繼承類中新定義了事件,應在此方法中引用觸發
506 /// 5.工作線程狀態不由工作方法管理,所以在工作方法中不應改變_taskState變量值
507 /// 6.工作方法中應對args參數進行有效檢查
508 /// </summary>
509 /// <param name="args">傳入的參數數組</param>
510 /// <returns>返回null</returns>
511 virtual public object Work(params object[] args )
512 {
513 System.Threading.Thread.CurrentThread.IsBackground = true;
514 _workThread = System.Threading.Thread.CurrentThread;
515 _result = null;
516 return null;
517 }
518
519 #endregion
520 }
521}
522
523使用Task類#region 使用Task類
524/**//*
525
526使用 Task 類
527
528一.在UI線程中創建Task類
529
530Task 類負責管理後臺線程。要使用 Task 類,必須做的事情就是創建一個 Task 對象,註冊它激發的事件,並且實現這些事件的處理。因爲事件是在 UI 線程上激發的,所以您根本不必擔心代碼中的線程處理問題。
531
532下面的示例展示瞭如何創建 Task 對象。現假設UI 有兩個按鈕,一個用於啓動運算,一個用於停止運算,還有一個進度欄顯示當前的計算進度。
533
534// 創建任務管理對象
535_Task = new Task();
536// 掛接任務管理對象工作狀態變化事件
537_Task.TaskStatusChanged += new TaskEventHandler( OnTaskStatusChanged );
538// 掛接任務管理對象工作進度變化事件
539_Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged );
540
541(1)
542用於計算狀態和計算進度事件的事件處理程序相應地更新 UI,例如通過更新狀態欄控件。
543
544private void OnTaskProgressChanged( object sender,TaskEventArgs e )
545{
546 _progressBar.Value = e.Progress;
547}
548(2)
549下面的代碼展示的 TaskStatusChanged 事件處理程序更新進度欄的值以反映當前的計算進度。假定進度欄的最小值和最大值已經初始化。
550
551private void OnTaskStatusChanged( object sender, TaskEventArgs e )
552{
553 switch ( e.Status )
554 {
555 case TaskStatus.Running:
556 button1.Enabled = false;
557 button2.Enabled = true;
558 break;
559 case TaskStatus.Stop:
560 button1.Enabled = true;
561 button2.Enabled = false;
562 break;
563 case TaskStatus.CancelPending:
564 button1.Enabled = false;
565 button2.Enabled = false;
566 break;
567 }
568}
569
570在這個示例中,TaskStatusChanged 事件處理程序根據計算狀態啓用和禁用啓動和停止按鈕。這可以防止用戶嘗試啓動一個已經在進行的計算,並且向用戶提供有關計算狀態的反饋。
571
572通過使用 Task 對象中的公共方法,UI 爲每個按鈕單擊實現了窗體事件處理程序,以便啓動和停止計算。例如,啓動按鈕事件處理程序調用 StartTask 方法,如下所示。
573
574private void startButton_Click( object sender, System.EventArgs e )
575{
576 _Task.StartTask( new object[] {} );
577}
578
579類似地,停止計算按鈕通過調用 StopTask 方法來停止計算,如下所示。
580
581private void stopButton_Click( object sender, System.EventArgs e )
582{
583 _Task.StopTask();
584}
585
586二.可能在非UI線程中使用Task類時
587(1)和(2)應作如下改變
588
589(1)
590用於計算狀態和計算進度事件的事件處理程序相應地更新 UI,例如通過更新狀態欄控件。
591
592private void OnTaskProgressChanged( object sender,TaskEventArgs e )
593{
594 if (InvokeRequired ) //不在UI線程上,異步調用
595 {
596 TaskEventHandler TPChanged = new TaskEventHandler( OnTaskProgressChanged );
597 this.BeginInvoke(TPChanged,new object[] {sender,e});
598 }
599 else //更新
600 {
601 _progressBar.Value = e.Progress;
602 }
603}
604(2)
605下面的代碼展示的 TaskStatusChanged 事件處理程序更新進度欄的值以反映當前的計算進度。假定進度欄的最小值和最大值已經初始化。
606
607private void OnTaskStatusChanged( object sender, TaskEventArgs e )
608{
609 if (InvokeRequired ) //不在UI線程上,異步調用
610 {
611 TaskEventHandler TSChanged = new TaskEventHandler( OnTaskStatusChanged );
612 this.BeginInvoke(TSChanged,new object[] {sender,e});
613 }
614 else //更新
615 {
616 switch ( e.Status )
617 {
618 case TaskStatus.Running:
619 button1.Enabled = false;
620 button2.Enabled = true;
621 break;
622 case TaskStatus.Stop:
623 button1.Enabled = true;
624 button2.Enabled = false;
625 break;
626 case TaskStatus.CancelPending:
627 button1.Enabled = false;
628 button2.Enabled = false;
629 break;
630 }
631 }
632}
633
634*/
635#endregion
636
三、示例
1.啓動時的UI界面
2.後臺工作方法(費用方法)運行後,任務狀態爲Running
3.強制中止工作方法,運行任務狀態Aborted
4.工作方法突發錯誤時,任務狀態ThrowErrorStoped
5.工作方法正常結束或正常取消而結束時,任務狀態Stopped
示例代碼下載