衔接UI线程和管理后台工作线程的类(多线程、异步调用) (转)
时间:2010-08-23 来源:akeforever
一、引言
在编写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 )方便调用。在实际使用时,可在继承类中定义多个相同调用接口的方法,避免重复编码,较为方便。
给大家作个参考,而大牛呢,多点指正。当是扔个砖头,想砸块玉吧。
二、代码
using System;2
using System.Windows.Forms;3

4
namespace Net66.AsynchThread5

{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
AbortPending35

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 Task151
{ 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
#endregion178

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
#endregion201

202
属性#region 属性203
204
/**//// <summary>205
/// 任务工作进程出错时,捕获的异常对象206
/// </summary>207
public Exception Exception208
{209
get
{ return _exception;}210
}211
/**//// <summary>212
/// 任务调用线程(前台或UI线程)213
/// </summary>214
public System.Threading.Thread CallThread215
{216
get
{ return _callThread;}217
}218
/**//// <summary>219
/// 任务工作线程(后台)220
/// </summary>221
public System.Threading.Thread WordThread222
{223
get
{return _workThread;}224
}225
226
/**//// <summary>227
/// 任务进度(0-100)228
/// </summary>229
public int Progress230
{231
get
{return _progress;} 232
}233
/**//// <summary>234
/// 任务工作状态235
/// </summary>236
public TaskStatus TaskState237
{238
get
{return _taskState;}239
}240
/**//// <summary>241
/// 任务工作结果242
/// </summary>243
public object Result244
{245
get
{return _result;}246
}247

248
protected bool IsStop249
{250
get251
{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
#endregion267

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
#endregion358

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.Stop398
/// </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
else438
{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
else474
{475
FireThrowErrorEvent(_progress,_exception);476
}477
}478
lock( this ) 479
{ 480
if (error)481
{482
if ( abort)483
{484
_taskState = TaskStatus.Aborted; //调用者强行中止485
}486
else487
{488
_taskState = TaskStatus.ThrowErrorStoped;//出现错误而中止489
}490
} 491
else492
{ _taskState = TaskStatus.Stopped;} //正常结束493
FireStatusChangedEvent( _taskState, _result);494
} 495
} 496
#endregion497

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
#endregion520
}521
}522

523
使用Task类#region 使用Task类524
/**//*525

526
使用 Task 类527

528
一.在UI线程中创建Task类529

530
Task 类负责管理后台线程。要使用 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

544
private void OnTaskProgressChanged( object sender,TaskEventArgs e ) 545
{ 546
_progressBar.Value = e.Progress; 547
} 548
(2)549
下面的代码展示的 TaskStatusChanged 事件处理程序更新进度栏的值以反映当前的计算进度。假定进度栏的最小值和最大值已经初始化。550

551
private 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

574
private void startButton_Click( object sender, System.EventArgs e ) 575
{ 576
_Task.StartTask( new object[] {} ); 577
} 578

579
类似地,停止计算按钮通过调用 StopTask 方法来停止计算,如下所示。580

581
private 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

592
private 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

607
private 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
#endregion636
三、示例
1.启动时的UI界面

2.后台工作方法(费用方法)运行后,任务状态为Running

3.强制中止工作方法,运行任务状态Aborted

4.工作方法突发错误时,任务状态ThrowErrorStoped

5.工作方法正常结束或正常取消而结束时,任务状态Stopped
http://files.cnblogs.com/net66/AsynchUI-2.rar










