C# WinForm多线程(三)Control.Invoke
下面我们就把在Windows Form软件中使用Invoke时的多线程要注意的问题给大家做一个介绍。
首先,什么样的操作需要考虑使用多线程?总的一条就是,负责与用户交互的线程(以下简称为UI线程)应该保持顺畅,当UI线程调用的API可能引起阻塞时间超过30毫秒时(比如访问CD-ROM等速度超慢的外设、进行远程调用等等)就应该考虑使用多线程。为什么是30毫秒?30毫秒的概念是人眼可以察觉到的一个迟滞,大约等同于电影里的一帧停留的时间,最长不要超过100毫秒。
第二,最方便和简单的多线程是使用线程池。通过线程池里的线程运行代码的最简便方法则是使用异步委托调用。注意委托调用通常是同步完成的,请使用BeginInvoke方法,这样就可以把要调用的方法排队到线程池里等候处理,而程序的流程会立刻返回到调用方(此处是UI线程),而调用方因此不会出现阻塞。
看看下面的例子我们就发现要使用线程池异步执行代码也并非十分复杂,这里我们利用System.Windows.Forms.MethodInvoker委托进行异步调用。注意MethodInvoker委托不接受方法参数,如果需要向异步执行的方法传递参数,请使用其他委托,或者需要自己定义。
Code
归纳上述方法,对UI线程而言实际上就是:1、发出调用,2、立刻返回,具体运行过程不理了,这样UI线程就不会被阻塞。这种方法很重要,下面我们会深入介绍。除了上面的方法,还有其他使用线程池的方法,当然如果你高兴也可以自己创建线程。
第三,在Windows Form中使用多线程的,最重要的一条注意事项是,除了创建控件的线程以外,绝对不要在任何其他线程里面调用控件的成员(只有极个别情况例外),也就是说控件属于创建它的线程,不能从其他线程里面访问。这一条适用于所有从System.Windows.Forms.Control派生的控件(因此可以说是几乎所有控件),包括Form控件本身也是。举一反三,我们很容易得出这样的结论,控件的子控件必须由创建控件的线程来创建,比如一个表单上的按钮,比如由创建表单的线程来创建,因此,一个窗口中的所有控件实际上都活在同一个线程之中。在实际编程时,大多数的软件的做法都是让同一线程负责全部的控件,这就是我们所说的UI线程。看下面的例子:
// 这是由UI线程定义的Label控件
private Label lblStatus;
.
// 以下方法不在UI线程上执行
private void RunsOnWorkerThread() {
DoSomethingSlow();
lblStatus.Text = "Finished!"; // 这是错的
}
我们要特别提醒大家,很多人刚开始的时候都会使用以上的方法来访问不在同一个线程里的控件(包括笔者本人),而且在1.0版.Net 框架上似乎没有发现问题,但是这根本就是错的,更糟糕的是,程序员在这里不会得到任何错误提示,一开始就上当受骗,之后会莫明其妙地发现其他错误,这就是Windows Form多线程编程的痛苦所在。笔者试过花很多时间来Debug自己写的Splash窗口突然消失的问题,结果还是失败了:笔者在软件的引导过程中,用另外一个线程里创建了一个Splash窗口来显示欢迎信息,然后尝试把主线程里引导的状态直接写入到Splash窗口上的控件中,开始还OK,可是过一会Splash窗口就莫明其妙消失了。
理解了这一点,我们应该留意到,有时候即使没有用System.Threading.Thread来显式创建一个线程,我们也可能因为使用了异步委托的BeginInvoke方法来隐式创建了线程(从线程池里),在这种线程里也同样不能调用UI线程所创建的控件的成员。
第四,由于上述限制,我们可能会感到很不方便,的确,当我们利用一个新创建的线程来执行某些花时间的运算时,怎样知道运算进度如何并通过UI反映给用户呢?解决方法很多!比如熟悉多线程编程的用户很快会想到,我们采用一些低级的同步方法,工作者线程把状态保存到一个同步对象中,让UI线程轮询(Polling)该对象并反馈给用户就可以了。不过,这还是挺麻烦的,实际上不用这样做,Control类(及其派生类)对象有一个Invoke方法很特别,这是少数几个不受线程限制的成员之一。我们前面说到,绝对不要在任何其他线程里面调用非本线程创建的控件的成员时,也说了“只有极个别情况例外”,这个Invoke方法就是极个别情况之一----Invoke方法可以从任何线程里面调用。下面我们来讲解Invoke方法。
Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程(即创建控件的线程)上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。注意,使用Invoke方法时,UI线程不能处于阻塞状态。以下MSDN里关于Invoke方法的说明:
“控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。
委托可以是 EventHandler 的实例,在此情况下,发送方参数将包含此控件,而事件参数将包含 EventArgs.Empty。委托还可以是 MethodInvoker 的实例或采用 void 参数列表的其他任何委托。调用 EventHandler 或 MethodInvoker 委托比调用其他类型的委托速度更快。”
好了,说完Invoke,顺便说说BeginInvoke,毫无疑问这是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,能够使用异步方法时应尽量使用异步方法。
下面我们利用所学到的知识来改写上面那个简单的例子:
Code
C# WinForm多线程(三)Control.Invoke的更多相关文章
- WinForm二三事(三)Control.Invoke&Control.BeginInvoke
http://www.cnblogs.com/yuyijq/archive/2010/01/11/1643802.html 这个系列从2009年写到2010年,差点又成太监文.随着WPF/Silver ...
- 浅述WinForm多线程编程与Control.Invoke的应用
VS2008.C#3.0在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来.一个最直接的方法便是使用多线程.多线程编程的方式在W ...
- WinForm多线程编程与Control.Invoke的应用浅谈
在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来.一个最直接的方法便是使用多线程.多线程编程的方式在WinForm开发中必不可 ...
- 【转】浅述WinForm多线程编程与Control.Invoke的应用
环境:VS2008.C#3.0 在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来.一个最直接的方法便是使用多线程.多线程编程的 ...
- WinForm多线程学习文档
基础篇 怎样创建一个线程 受托管的线程与 Windows线程 前台线程与后台线程 名为BeginXXX和EndXXX的方法是做什么用的 异步和多线程有什么关联 WinForm多线程编程篇 我的多线程W ...
- C# winform多线程的小例子
在文本框中输入一个数字,点击开始累加按钮,程序计算从1开始累计到该数字的结果.因为该累加过程比较耗时,如果直接在UI线程中进行,那么当前窗口将出现假死.为了有更好的用户体验,程序启动一个新的线程来单独 ...
- WinForm多线程编程简单Demo
需要搭建一个可以监控报告生成的CS(WinForm)工具,即CS不断Run,执行获取数据生成报告,经过研究和实践,选择了使用"WinForm多线程编程"的解决方案.当然参考了园中相 ...
- Control.Invoke和Control.BeginInvoke
问题的引入 下面有个简单的demo,大家一看代码就知道效果如何示例.我新建一个winform的程序,然后写入了如下代码: using System; using System.Windows.Form ...
- 千万别在UI线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死
原文地址:https://www.cnblogs.com/wangchuang/archive/2013/02/20/2918858.html .c# Invoke和BeginInvoke 区别 Co ...
- c#winform多线程感想
我很菜所以好好学!!! 最近在做一个关于识别的项目,手动识别和自动识别,为了更好的保证自动识别不会引起界面的卡顿等现象,所以简单的学习了一下多线程,也只是入门但还是记录一下. 一.首先了解一下用多线程 ...
随机推荐
- syslog-ng 3.4.3 发布,强烈建议升级 - 开源中国社区
syslog-ng 3.4.3 发布,强烈建议升级 - 开源中国社区 syslog-ng 3.4.3 发布,强烈建议升级 oschina 发布于: 2013年08月16日 (2评) 分享到 ...
- vijos P1352 最大获利(最小割)
请不要随便指点别人该怎么做.每一个人的人生都应该自己掌握.你给不了别人一切.你也不懂别人的忧伤. 微笑不代表快乐.哭泣不一定悲伤 不努力怎么让关心你的人幸福.不努力怎么让看不起你的人绝望. 我用生命在 ...
- C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符
重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪 ...
- 设置SVN忽略文件和文件夹(文件夹)
在多数项目中你总会有文件和文件夹不须要进行版本号控制.这可能包含一些由编译器生成的文件,*.obj,*.lst,也许是一个用于存放可运行程序的输出文件夹.仅仅要你提交改动,TortoiseSVN 就会 ...
- hbase总结(二)-hbase安装
本篇介绍两种HBase的安装方式:本地安装方式和伪分布式安装方式. 安装的前提条件是已经安装成功了hadoop,并且hadoop的版本号要和hbase的版本号相匹配. 我将要安装的hbase是hbas ...
- hdu1069(dp)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1069 分析: 每种石头有六种方法,那么等效为:有6*n种石头. 根据x和y排序(要保证相应的x.y总有 ...
- Android从raw、assets、SD卡中获取资源文件内容
先顺带提一下,raw文件夹中的文件会和project一起经过编译,而assets里面的文件不会~~~ 另外,SD卡获取文件需要权限哦! //从res文件夹中的raw 文件夹中获取文件并读取数据 p ...
- linux+nginx+mysql+php
LNMP(linux+nginx+mysql+php)服务器环境配置 一.简介 Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为 “engine X”, 是一个高性能的 ...
- HTML解析HtmlAgilityPack
原文:HTML解析HtmlAgilityPack //解析页面源代码 Uri surl = new Uri(url); Uri uriCategory = ...
- Python数据结构之注意事项
1.列表 列表是Python中使用最频繁的数据结构,列表提供很多函数操作,比如下标存取,分片,index,append,remove等等. 例如: list=[1,2,'hello','python ...