配色: 字号:
走进异步编程的世界 - 在 WinForm 中执行异步操作
2016-09-21 | 阅:  转:  |  分享 
  
走进异步编程的世界-?在WinForm中执行异步操作



?主要介绍在WinForm中如何执行异步操作。

?

目录

?

一、在WinForm程序中执行异步操作

下面通过窗体示例演示以下操作-点击按钮后:

将按钮禁用,并将标签内容改成:“Doing”(表示执行中);

线程挂起3秒(模拟耗时操作);

启用按钮,将标签内容改为:“Complete”(表示执行完成)。

1publicpartialclassForm1:Form

2{

3publicForm1()

4{

5InitializeComponent();

6}

7

8privatevoidbtnDo_Click(objectsender,EventArgse)

9{

10btnDo.Enabled=false;

11lblText.Text=@"Doing";

12

13Thread.Sleep(3000);

14

15btnDo.Enabled=true;

16lblText.Text=@"Complete";

17}

18}

可是执行结果却是:



图1-1

?

【发现的问题】

①好像没有变成“Doing”?

并且拖动窗口的时候卡住不动了?

3秒后突然变到想拖动到的位置?

同时文本变成“Complete”?

?

【分析】GUI程序在设计中要求所有的显示变化都必须在主GUI线程中完成,如点击事件和移动窗体。Windows?程序时通过消息来实现,消息放入消息泵管理的消息队列中。点击按钮时,按钮的Click消息放入消息队列。消息泵从队列中移除该消息,并开始处理点击事件的代码,即?btnDo_Click事件的代码。

btnDo_Click事件会将触发行为的消息放入队列,但在?btnDo_Click时间处理程序完全退出前(线程挂起3秒退出前),消息都无法执行。(3秒后)接着所有行为都发生了,但速度太快肉眼无法分辨才没有发现标签改成“Doing”。



图1-2点击事件



图1-3点击事件具体执行过程



现在我们加入async/await特性。

1publicpartialclassForm1:Form

2{

3publicForm1()

4{

5InitializeComponent();

6}

7

8privateasyncvoidbtnDo_Click(objectsender,EventArgse)

9{

10btnDo.Enabled=false;

11lblText.Text=@"Doing";

12

13awaitTask.Delay(3000);

14

15btnDo.Enabled=true;

16lblText.Text=@"Complete";

17}

18}



图1-4

现在,就是原先希望看到的效果。

【分析】btnDo_Click事件处理程序先将前两条消息压入队列,然后将自己从处理器移出,在3秒后(等待空闲任务完成后Task.Delay)再将自己压入队列。这样可以保持响应,并保证所有的消息可以在线程挂起的时间内被处理。

?

?1.1Task.Yield

Task.Yield方法创建一个立刻返回的awaitable。等待一个Yield可以让异步方法在执行后续部分的同时返回到调用方法。可以将其理解为离开当前消息队列,回到队列末尾,让CPU有时间处理其它任务。

1classProgram

2{

3staticvoidMain(string[]args)

4{

5constintnum=1000000;

6vart=DoStuff.Yield1000(num);

7

8Loop(num/10);

9Loop(num/10);

10Loop(num/10);

11

12Console.WriteLine($"Sum:{t.Result}");

13

14Console.Read();

15}

16

17///

18///循环

19///


20///

21privatestaticvoidLoop(intnum)

22{

23for(vari=0;i
24}

25}

26

27internalstaticclassDoStuff

28{

29publicstaticasyncTaskYield1000(intn)

30{

31varsum=0;

32for(inti=0;i
33{

34sum+=i;

35if(i%1000==0)

36{

37awaitTask.Yield(www.hunanwang.net);//创建异步产生当前上下文的等待任务

38}

39}

40

41returnsum;

42}

43}



?图1.1-1

上述代码每执行1000次循环就调用Task.Yield方法创建一个等待任务,让处理器有时间处理其它任务。该方法在GUI程序中是比较有用的。

?

二、在WinForm中使用异步Lambda表达式

将刚才的窗口程序的点击事件稍微改动一下。

1publicpartialclassForm1:Form

2{

3publicForm1()

4{

5InitializeComponent();

6

7//async(sender,e)异步表达式

8btnDo.Click+=async(sender,e)=>

9{

10Do(false,"Doing");

11

12awaitTask.Delay(3000);

13

14Do(true,"Finished");

15};

16}

17

18privatevoidDo(boolisEnable,stringtext)

19{

20btnDo.Enabled=isEnable;

21lblText.Text=text;

22}

23}

还是原来的配方,还是熟悉的味道,还是原来哪个窗口,变的只是内涵。



图2-1

?

三、一个完整的WinForm程序

现在在原来的基础上添加了进度条,以及取消按钮。

1publicpartialclassForm1:Form

2{

3privateCancellationTokenSource_source;

4privateCancellationToken_token;

5

6publicForm1()

7{

8InitializeComponent();

9}

10

11///

12///Do按钮事件

13///


14///

15///

16privateasyncvoidbtnDo_Click(objectsender,EventArgse)

17{

18btnDo.Enabled=false;

19

20_source=newCancellationTokenSource();

21_token=_source.Token;

22

23varcompletedPercent=0;//完成百分比

24constinttime=10;//循环次数

25constinttimePercent=100/time;//进度条每次增加的进度值

26

27for(vari=0;i
28{

29if(_token.IsCancellationRequested)

30{

31break;

32}

33

34try

35{

36awaitTask.Delay(500,_token);

37completedPercent=(i+1)timePercent;

38}

39catch(Exception)

40{

41completedPercent=itimePercent;

42}

43finally

44{

45progressBar.Value=completedPercent;

46}

47}

48

49varmsg=_token.IsCancellationRequested?$"进度为:{completedPercent}%已被取消!":$"已经完成";

50

51MessageBox.Show(msg,@"信息");

52

53progressBar.Value=0;

54InitTool();

55}

56

57///

58///初始化窗体的工具控件

59///


60privatevoidInitTool()

61{

62progressBar.Value=0;

63btnDo.Enabled=true;

64btnCancel.Enabled=true;

65}

66

67///

68///取消事件

69///


70///

71///

72privatevoidbtnCancel_Click(objectsender,EventArgse)

73{

74if(btnDo.Enabled)return;

75

76btnCancel.Enabled=false;

77_source.Cancel(www.visa158.com);

78}

79}



?图3-1

?

四、另一种异步方式-BackgroundWorker类

与async/await不同的是,你有时候可能需要一个额外的线程,在后台持续完成某项任务,并不时与主线程通信,这时就需要用到?BackgroundWorker类。主要用于GUI程序。

书中的千言万语不及一个简单的示例。

1publicpartialclassForm2:Form

2{

3privatereadonlyBackgroundWorker_worker=newBackgroundWorker();

4

5publicForm2()

6{

7InitializeComponent();

8

9//设置BackgroundWorker属性

10_worker.WorkerReportsProgress=true;//能否报告进度更新

11_worker.WorkerSupportsCancellation=true;//是否支持异步取消

12

13//连接BackgroundWorker对象的处理程序

14_worker.DoWork+=_worker_DoWork;//开始执行后台操作时触发,即调用BackgroundWorker.RunWorkerAsync时触发

15_worker.ProgressChanged+=_worker_ProgressChanged;//调用BackgroundWorker.ReportProgress(System.Int32)时触发

16_worker.RunWorkerCompleted+=_worker_RunWorkerCompleted;//当后台操作已完成、被取消或引发异常时触发

17}

18

19///

20///当后台操作已完成、被取消或引发异常时发生

21///


22///

23///

24privatevoid_worker_RunWorkerCompleted(objectsender,RunWorkerCompletedEventArgse)

25{

26MessageBox.Show(e.Cancelled?$@"进程已被取消:{progressBar.Value}%":$@"进程执行完成:{progressBar.Value}%");

27progressBar.Value=0;

28}

29

30///

31///调用BackgroundWorker.ReportProgress(System.Int32)时发生

32///


33///

34///

35privatevoid_worker_ProgressChanged(objectsender,ProgressChangedEventArgse)

36{

37progressBar.Value=e.ProgressPercentage;//异步任务的进度百分比

38}

39

40///

41///开始执行后台操作触发,即调用BackgroundWorker.RunWorkerAsync时发生

42///


43///

44///

45privatestaticvoid_worker_DoWork(objectsender,DoWorkEventArgse)

46{

47varworker=senderasBackgroundWorker;

48if(worker==null)

49{

50return;

51}

52

53for(vari=0;i<10;i++)

54{

55//判断程序是否已请求取消后台操作

56if(worker.CancellationPending)

57{

58e.Cancel=true;

59break;

60}

61

62worker.ReportProgress((i+1)10);//触发BackgroundWorker.ProgressChanged事件

63Thread.Sleep(250);//线程挂起250毫秒

64}

65}

66

67privatevoidbtnDo_Click(objectsender,EventArgse)

68{

69//判断BackgroundWorker是否正在执行异步操作

70if(!_worker.IsBusy)

71{

72_worker.RunWorkerAsync();//开始执行后台操作

73}

74}

75

76privatevoidbtnCancel_Click(objectsender,EventArgse)

77{

78_worker.CancelAsync();//请求取消挂起的后台操作

79}

80}



献花(0)
+1
(本文系白狐一梦首藏)