如果说async给ASP.NET带来的是处理能力的提高,那么在WinForm中给程序员带来的好处则是最大的。我们再也不用因为要实现异步写回调或者绑定事件了,省事了,可读性也提高了。不信你看下面我们将调用我们那个web service的代码在.NET4.5下实现一下:
1 2 3 4 5 6 7 |
|
简单的三行代码,像写同步代码一样写异步代码,我想也许这就是async/await的魔力吧。在await之后,UI线程就可以回去响应UI了,在上面的代码中我们是没有新线程产生的,和EAP一样拿到结果直接就可以对UI操作了。
async/await似乎真的很好,但是如果我们await后面的代码执行在另外一个线程中会发生什么事情呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
我们在界面中放了一个ProgressBar,同时开一个线程去把从1到5000000的平方全部加起来,看起来是一个非常耗时的操作,于是我们用Task.Run开了一个新的线程去执行。(注:如果是纯运算的操作,多线程操作对性能没有多大帮助,我们这里主要是想给UI一个进度显示当前进行到哪一步了。)看起来没有什么问题,我们按F5运行吧!
Bomb~
当执行到这里的时候,程序就崩溃了,告诉我们”无效操作,只能从创建porgressBar的线程访问它。“ 这也是我们一开始提到的,在WinForm程序中,只有UI主线程才能对UI进行操作,其它的线程是没有权限的。接下来我们就来看看,如果在WinForm中实现非UI线程对UI控制的更新操作。
万能的Invoke
WinForm中绝大多数的控件包括窗体在内都实现了Invoke方法,可以传入一个Delegate,这个Delegate将会被拥有那个控制的线程所调用,从而避免了跨线程访问的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Desktop.vshost.exe Information: 0 : UI Thread : 9 Desktop.vshost.exe Information: 0 : Run calculation on thread: 10 Desktop.vshost.exe Information: 0 : Update UI on thread: 9
Invoke方法比较简单,我们就不做过多的研究了,但是我们要考虑到一点,Invoke是WinForm实现的UI跨线程沟通方式,WPF用的却是Dispatcher,如果是在ASP.NET下跨线程之间的同步又怎么办呢。为了兼容各种技术平台下,跨线程同步的问题,Microsoft在.NET2.0的时候就引入了我们下面的这个对象。
SynchronizationContext上下文同步对象
为什么需要SynchronizationContext
就像我们在WinForm中遇到的问题一样,有时候我们需要在一个线程中传递一些数据或者做一些操作到另一个线程。但是在绝大多数情况下这是不允许的,出于安全因素的考虑,每一个线程都有它独立的内存空间和上下文。因此在.NET2.0,微软推出了SynchronizationContext。
它主要的功能之一是为我们提供了一种将一些工作任务(Delegate)以队列的方式存储在一个上下文对象中,然后把这些上下文对象关联到具体的线程上,当然有时候多个线程也可以关联到同一个SynchronizationContext对象。获取当前线程的同步上下文对象可以使用SynchronizationContext.Current。同时它还为我们提供以下两个方法Post和Send,分别是以异步和同步的方法将我们上面说的工作任务放到我们SynchronizationContext的队列中。
SynchronizationContext示例
还是拿我们上面Invoke中用到的例子举例,只是这次我们不直接调用控件的Invoke方法去更新它,而是写了一个Report的方法专门去更新UI。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
每一次操作完之后我们调用一下Report方法,把我们总共要算的数字,以及当前正在计算的数字传给它就可以了。接下来就看我们的Report方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
整个操作看起来要比Inovke复杂一点,与Invoke不同的是SynchronizationContext不需要对Control的引用,而Invoke必须先得有那个控件才能调用它的Invoke方法对它进行操作。