衔接UI线程和管理后台工作线程的类(多线程、异步调用)
一、引言
在编写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
示例代码下载