tag:blogger.com,1999:blog-37052745153074554212024-02-20T14:57:35.814-08:00Gone with wind自有天逸http://www.blogger.com/profile/07080657973269089903noreply@blogger.comBlogger2125tag:blogger.com,1999:blog-3705274515307455421.post-27718682512528818532006-12-26T00:09:00.001-08:002006-12-26T00:39:26.426-08:00WinForm和WPF中在工作者线程里更新UI对象<p>这应该是每一个做客户端编程的人都曾经遇到过的问题:进行一个耗时较长的运算,并且在运算完成后需要更新UI对象的状态。一般来说这种运算(或者调用)我们会放在一个工作者线程中进行,完成了运算(或调用)之后,再根据运算结果通过一定的机制更新UI对象的状态。在WinForm开发中是通过Invoke方法来完成的。Invoke是在System.Windows.Forms.Control里面实现的,由于这个Control是WinForm所有UI元素类的基类,所以所有的WinForm UI对象都可以提供Invoke调用来实现从工作者线程到UI线程的切换。但是Invoke需要一个delegate参数,下面是一个简单的示例代码:<br /></p><br /><blockquote><br /><p style="COLOR: rgb(51,0,153);font-family:verdana;" ><span style="font-size:85%;">// This method is run on a worker thread<br />private void DoWork()<br />{<br />...<br />// Do Some works<br /><strong>this.Invoke((MethodInvoker)delegate(){ UpdateUI();});<br /></strong>}<br /></span></p><br /><p><span style="COLOR: rgb(51,0,153);font-size:85%;" ><span style="font-family:verdana;">// This method will update ui element</span><br /><span style="font-family:verdana;">private void UpdateUI()</span><br /><span style="font-family:verdana;">{</span><br /><span style="font-family:verdana;">}</span></span><br /></p></blockquote><br /><p>这里我们甚至可以进一步改进UpdateUI方法,使其可以适应在UI线程内部被调用和被工作者线程中调用的两种不同情况,这是通过System.Windows.Forms.Control.InvokeRequired来实现的:<br /></p><br /><blockquote><br /><p style="COLOR: rgb(51,0,153)">// This method is run on a worker thread<br />private void DoWork()<br />{<br />...<br />// Do Some works<br />UpdateUI();<br />}<br /></p><br /><p><span style="COLOR: rgb(51,0,153)">// This method will update ui element</span><br /><span style="COLOR: rgb(51,0,153)">private void UpdateUI()</span><br /><span style="COLOR: rgb(51,0,153)">{</span><br /><strong style="COLOR: rgb(51,0,153)">if (InvokeRequired)<br />{<br />Invoke((MethodInvoker)delegate(){UpdateUI();});<br />}</strong><br /><span style="COLOR: rgb(51,0,153)">else</span><br /><span style="COLOR: rgb(51,0,153)">{</span><br /><span style="COLOR: rgb(51,0,153)">// Real update code</span><br /><span style="COLOR: rgb(51,0,153)">...</span><br /><span style="COLOR: rgb(51,0,153)">}</span><br /><span style="COLOR: rgb(51,0,153)">}</span><br /></p></blockquote><br /><p>在.NET Framework2.0里,提供了一个新的类:BackgroundWorker,更简单的实现了工作者线程完成后更新UI对象的问题。BackgroundWorker提供了DoWork和RunWorkerCompleted这两个event供开发者挂接自己的delegate方法(当然还有其他event,但是先让我们只关注这两个)。DoWork挂接的delegate将会在.NET Thread Pool上面执行,而RunWorkerCompleted挂接的delegate将会在创建BackgroundWorker的线程上执行,一般来说也就是UI线程,所以我们可以在RunWorkerCompleted挂接的delegate里面自由的修改UI元素的状态而不用担心出现什么问题。BackgroundWorker还有其他一些方便的功能例如中途取消操作,操作进度报告等等,这里就不详细描述了。<br /></p><br /><p>在WPF里,所有的UI元素类都是重新实现的,机制与WinForm大不相同,也不从System.Windows.Forms.Control派生,所以也没有Invoke方法。那么在WPF里面应该怎么样处理工作者线程到UI线程的切换呢?这里需要使用Dispatcher。我们首先来看一下WPF里面UI对象的继承体系:<br /><img style="DISPLAY: block; MARGIN: 0px auto 20px; WIDTH: 417px; CURSOR: hand; TEXT-ALIGN: center" alt="" src="http://farm1.static.flickr.com/57/330817329_a55b28e82a.jpg" border="0" />Dispathcer这个Property就是在DispatcherObject里面引入的。WPF里面的UI控件要嘛就是从图中的Control派生的,要嘛就是从FrameworkElement派生的,所以都包含了Dispatcher这个Property。使用的时候跟WinForm里面是很类似的:<br /></p><br /><blockquote><br /><p><span style="COLOR: rgb(51,0,153)">void DoWork()</span><br /><span style="COLOR: rgb(51,0,153)">{</span><br /><span style="COLOR: rgb(51,0,153)">// do work</span><br /><span style="COLOR: rgb(51,0,153)">...</span><br /><span style="COLOR: rgb(51,0,153)">// call dispatcher to update ui</span><br /><strong style="COLOR: rgb(51,0,153)">Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (System.Windows.Forms.MethodInvoker)delegate() { UpdateUI(); });<br /></strong><span style="COLOR: rgb(51,0,153)">}</span><br /></p></blockquote>不过Dispatcher有一个问题,它不像WinForm里面,有一个InvokeRequired Property指示是否需要进行线程切换,所以在这里就不管是否是在UI线程内部进行的调用,都会进行一次线程切换了。<br /><br /><p class="zoundry_bw_tags"><br /><!-- Tag links generated by Zoundry Blog Writer. Do not manually edit. http://www.zoundry.com --><br /><span class="ztags"><span class="ztagspace">Technorati</span> : <a class="ztag" href="http://technorati.com/tag/WPF%20WinForm%20UI%20Programming" rel="tag">WPF WinForm UI Programming</a></span><br /></p>自有天逸http://www.blogger.com/profile/07080657973269089903noreply@blogger.com0tag:blogger.com,1999:blog-3705274515307455421.post-41277636529251368772006-12-26T00:06:00.000-08:002006-12-26T00:09:27.945-08:00System.Windows.Forms.Timer和System.Timers.Timer的区别<p>.NET Framework里面提供了三种Timer:<br /></p><ul><li>System.Windows.Forms.Timer</li><br /><li>System.Timers.Timer</li><br /><li>System.Threading.Timer<br /></li></ul><p>在我的记忆中,Visual Studio 2003的工具箱里面默认提供了System.Windows.Forms.Timer和System.Timers.Timer两种,而Visual Studio 2005中确只默认提供了System.Windows.Forms.Timer这一种。这里简单的介绍一下这两种Timer的区别。<br /></p><p>System.Windows.Forms.Timer是使用得比较多的Timer,Timer Start之后定时(按设定的Interval)调用挂接在Tick事件上的EvnetHandler。在这种Timer的EventHandler中可以直接获取和修改UI元素而不会出现问题--因为这种Timer实际上就是在UI线程自身上进行调用的。也正是因为这个原因,导致了在Timer的EventHandler里面进行长时间的阻塞调用,将会阻塞界面响应的后果。下面是一个简单的例子:<br /></p><blockquote style="COLOR: rgb(0,0,128)"><p style="font-family:verdana;"><span style="font-size:85%;">public class MainForm : Form<br />{<br />private void MainForm_Load(object sender, EventArgs e)<br />{<br />timer.Interval = 1000;<br />timer.Tick += delegate(object o, EventArgs args)<br />{<br />DoWork();<br />};<br />timer.Start();<br />}</span><span style="font-size:85%;"><br /></span></p><p><span style="font-family:Tahoma,sans-serif;font-size:85%;">private void DoWork()<br />{<br />for (int i = 0; i < 10; i++)<br />{<br />System.Threading.Thread.Sleep(1000);<br />}<br />}<br />System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();<br />}</span><br /></p></blockquote><p>在这个例子中,DoWork方法里面将会阻塞10秒,在这10秒之内,UI将会失去响应。而通过使用System.Timers.Timer,就可以解决这个问题。因为System.Timers.Timer是在.NET的Thread Pool上面运行的,而不是直接在UI Thread上面运行,所以在这种Timer的EventHandler里面进行耗时较长的计算不会导致UI失去响应。但是这里有两个地方需要注意:<br /></p><ol><li>因为一般来说System.Timers.Timer不是运行在UI Thread上面的,所以如果要在这种Timer的EventHandler里面更新UI元素的话,需要进行一次线程切换,在WinForm开发中一般通过UI元素的Invoke方法完成:<br /><blockquote style="COLOR: rgb(0,0,128);font-family:Tahoma,sans-serif;font-size:0.9em;" ><p style="font-family:verdana;"><span style="font-size:85%;">private void DoWork()<br />{<br />for (int i = 0; i < 10; i++)<br />{<br />System.Threading.Thread.Sleep(1000);<br />}<br />this.Invoke(new UpdateUICallBack(UpdateUI));<br />}<br /></span></p><p style="font-family:verdana;"><span style="font-size:85%;">private delegate void UpdateUICallBack();<br /></span></p><p><span style="font-size:85%;"><span style="font-family:verdana;">private void UpdateUI()</span><br /><span style="font-family:verdana;">{</span><br /><span style="font-family:verdana;">}</span></span><br /></p></blockquote></li><li>System.Timers.Timer有一个Property:<strong>SynchronizingObject</strong> 。如果设置了这个Property(一般是某个Form),那么之后对Timer挂接的EventHandler的调用将会在创建这个UI元素的线程上进行(一般来说就是UI线程)。值得注意的是,如果你通过WinForm设计器把System.Timers.Timer拖放到Form上,那么这个Property将会自动被设置。此时这种Timer就和System.Windows.Forms.Timer的效果一样:长调用将会阻塞界面。</li><br /></ol><br /><p class="zoundry_bw_tags"><br /><!-- Tag links generated by Zoundry Blog Writer. Do not manually edit. http://www.zoundry.com --><br /><span class="ztags"><span class="ztagspace">Technorati</span> : <a class="ztag" href="http://technorati.com/tag/.NET%20Timer%20UI%20Programming" rel="tag">.NET Timer UI Programming</a></span><br /></p>自有天逸http://www.blogger.com/profile/07080657973269089903noreply@blogger.com0