前言

在单线程中设置窗体某个控件的值很简单的事,只需要设置控件文本的值就可以了,但是有的业务场景很是复杂,界面上的控件也很多,这种情况下当数据量比较多的时候,在单线程中更新UI不可避免地会发生假死或卡顿现象,用户体验十分不爽,所以必须采用多线程来处理数据和UI。但是如果直接添加一个线程来更新控件信息,就会抛出错误,很显然微软并不希望我们这样做,因为UI控件不是线程安全的,如果随意地在任何线程中改变控件的值,会发生各种奇怪的问题,多个线程间会争夺资源,没有秩序地更改控件的值,显然这是我们不想看到的结果。当然,解决这个问题的方式有很多,在此列出几种常用的方法,做一下记录,温故而知新。

不检测线程间冲突

由于业务的变更,导致界面控件和数据有所增加,单线程情况下性能堪忧,这时就需要从单线程切换到多线程,我们写的代码可能是这样:

  1. private void btnSetOrg_Click(object sender, EventArgs e)
  2. {
  3. Thread t = new Thread(new ParameterizedThreadStart(SetOrgValue));
  4. //Thread t = new Thread(SetOrgValue); 效果同上
  5. t.Start("Organization");
  6. }
  7.  
  8. void SetOrgValue(object obj)
  9. {
  10. this.Org.Text = obj.ToString();
  11. }

上述代码创建一个新的线程,并在新的线程中为Org控件赋值,看起来没什么问题,但当我们运行的时候,会报一个错误:线程间操作无效: 从不是创建控件“Org”的线程访问它。就是说上述代码中的t不是创建TextBox的线程,所以不能访问Org控件的属性。默认只能由创建该控件的线程去访问该控件的数据,否则会导致读写不一致。操作系统中介绍过PV操作,对此有很好的解释。对此有一个极为简单的方法可以避免上述错误,那就是不检测线程间冲突,只需要在构造函数中添加一行代码即可:

  1. public UIForm()
  2. {
  3.  
  4. InitializeComponent();
  5. Control.CheckForIllegalCrossThreadCalls = false; //不检测跨线程调用
  6. }
  7.  
  8. private void btnSetOrg_Click(object sender, EventArgs e)
  9. {
  10. Thread t = new Thread(new ParameterizedThreadStart(SetOrgValue));
  11. //Thread t = new Thread(SetOrgValue); 效果同上
  12. t.Start("Organization");
  13. }
  14.  
  15. void SetOrgValue(object obj)
  16. {
  17. this.Org.Text = obj.ToString();
  18. }

这段代码显然是为了应付线程间操作无效的错误,关闭了跨线程访问UI的检测,允许多线程随意更改控件数据,但最后控件的属性是什么值,只有天知道,所以并不提倡使用该方法。

常用的多线程访问UI控件方式

  • 利用Delegate调用
  • 利用BackgroundWorker
  • 利用SynchronizationContext上下文
  • 利用Dispatcher.BeginInvoke

首先说说委托调用的方式更新UI控件的值。每个控件都有一个Bool类型的InvokeRequired属性,表示该控件是否存在创建该控件以外的线程要访问该控件,如果值为True说明存在这样的线程,那么就需要利用委托调用来更新控件的值。把第二部分的代码变更如下:

  1. delegate void SetValue(object obj);
  2.  
  3. private void btnSetOrg_Click(object sender, EventArgs e)
  4. {
  5. Thread t = new Thread(new ParameterizedThreadStart(SetOrgValue));
  6. //Thread t = new Thread(SetOrgValue); 效果同上
  7. t.Start("Organization");
  8. }
  9.  
  10. private void UpdateOrg(object obj)
  11. {
  12.  
  13. if (Org.InvokeRequired)//如果是非创建该控件的线程访问该控件
  14. {
  15. SetValue set = SetOrgValue;
  16. Org.Invoke(set, obj);
  17. }
  18. else
  19. {
  20. Org.Text = obj.ToString();
  21. }
  22. }
  23.  
  24. private void SetOrgValue(object obj)
  25. {
  26. this.Org.Text = obj.ToString();
  27. }

采用委托的方式,首先要检查访问控件的线程是否是创建该控件的线程,如果不是的话,就采用委托的方式去调用设值的方法。如果从另一个线程调用控件的方法,那么必须使用控件的Invoke方法来将调用封送到适当的线程。网上有一个有趣的比喻,如果某人向你(Org控件)借钱(访问并修改),如果直接去你的钱包拿(设置Org的属性值)是很不安全的,万一直接抢走了呢?所以需要提前告诉你一声说我需要借钱(委托),然后自己拿出钱(线程安全)借给借钱的人。这样理解起来就比较容易接受了。

第二种方式是利用BackgroundWorker来访问控件,其实质上也是创建了新线程,只不过BackgroundWorker做了一个封装,使我们用起来更方便一些。

  1. private void btnSetOrg_Click(object sender, EventArgs e)
  2. {
  3. using (BackgroundWorker bw = new BackgroundWorker())
  4. {
  5. bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
  6. bw.DoWork += new DoWorkEventHandler(DoWork);
  7. bw.RunWorkerAsync("Organization");
  8. }
  9. }
  10.  
  11. void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  12. {
  13. //这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了
  14. this.textBox1.Text = e.Result.ToString();
  15. MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());//查看当前线程的ID
  16. }
  17.  
  18. void DoWork(object sender, DoWorkEventArgs e)
  19. {
  20. MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());//查看当前线程的ID
  21. e.Result = e.Argument;//这里只是简单的把参数当做结果返回,当然也可以在这里做复杂的处理后,再返回自己想要的结果(这里的操作是在另一个线程上完成的)
  22. }

第三种方式就是利用SynchronizationContext上下文。这种方式相对于上面的几种方式不是很常用。使用方式如下:

  1. private void btnSet_Click(object sender, EventArgs e)
  2. {
  3. Thread t = new Thread(new ParameterizedThreadStart(Run));
  4. OrgParam _org = new OrgParam() { Context = SynchronizationContext.Current, Org = "Organization" };
  5. t.Start(_org);
  6. }
  7. void Run(object obj)
  8. {
  9. OrgParam org = obj as OrgParam;
  10. org.Context.Post(SetTextValue, org.Org);
  11. }
  12.  
  13. void SetTextValue(object obj)
  14. {
  15. this.Org.Text = obj.ToString();
  16. }
  17. public class OrgParam
  18. {
  19. public SynchronizationContext Context { set; get; }
  20. public object Org { set; get; }
  21. }

最后一种就是利用Dispatcher.BeginInvoke来更新控件的值。这种方式相对来说比较方便,但是也不是很常用。

  1. private void btnSetOrg_Click(object sender, EventArgs e)
  2. {
  3. Thread t = new Thread(new ParameterizedThreadStart(SetOrgValue));
  4. t.Start("Orgnization");
  5. }
  6. private void SetOrgValue(object obj)
  7. {
  8. this.Dispatcher.BeginInvoke(() => { this.txt.Text = text.ToString(); });
  9. }

总结

以上就是多线程下更新控件值的几种方式,虽然方式不一样,但都是为了解决多线程访问控件出现的问题。其中不检测线程间冲突的方法和采用委托的方式只限于WinForm下使用;而最后一种Dispatcher.BeginInvoke只限于Silverlight下使用;而BackgroundWorkerSynchronizationContext这两种方式是适用于Winform/Silverlight的。以前在写WinForm的时候,只知道采用委托的方式和BackgroundWorker来解决跨线程访问控件的问题,现在回头看看这块的内容,又知道了几种解决问题的方式,也许这就是孔子老人家所说的“温故而知新”吧。马上就要过年了,提前祝大家新年快乐!

作者:悠扬的牧笛

博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6262790.html

声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

【基础】多线程更新窗体UI的若干方法的更多相关文章

  1. 富客户端 wpf, Winform 多线程更新UI控件

    前言 在富客户端的app中,如果在主线程中运行一些长时间的任务,那么应用程序的UI就不能正常相应.因为主线程要负责消息循环,相应鼠标等事件还有展现UI. 因此我们可以开启一个线程来格外处理需要长时间的 ...

  2. 拒绝卡顿——在WPF中使用多线程更新UI

    原文:拒绝卡顿--在WPF中使用多线程更新UI 有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例: public partial class MainW ...

  3. Android多线程更新UI的方式

    Android下,对于耗时的操作要放到子线程中,要不然会残生ANR,本次我们就来学习一下Android多线程更新UI的方式. 首先我们来认识一下anr: anr:application not rep ...

  4. OkHttp3几个简单的例子和在子线程更新UI线程的方法

    okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿. ...

  5. WPF多线程更新UI的一个解决途径

    那么该如何解决这一问题呢?通常的做法是把耗时的函数放在线程池执行,然后切回主线程更新UI显示.前面的updateTime函数改写如下: private async void updateTime()  ...

  6. c#运用this.invoke() 在多线程时对UI进行修改

    什么是进程呢?当一个程序开始运行时,它就是一个进程,进程所指包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈 ...

  7. iOS基础 - 多线程线程

    一.多线程的应用 l 充分发挥多核处理器的优势,并发(同时执行)执行任务让系统运行的更快.更流畅 二.进程与线程概念 l 一个运行的程序就是一个进程或者叫做一个任务 l 一个进程至少包含一个线程,线程 ...

  8. PDG转图像、PDF的若干方法

    作者:马健邮箱:stronghorse_mj@hotmail.com发布:2006.05.26更新:2008.08.24 补充说明:此文成文较早,其中对Pdg2Pic.FreePic2Pdf的描述早已 ...

  9. 多线程更新已排序的Datagridview数据,造成数据错位

    多线程更新已排序的Datagridview数据,触发Datagridview的auto-sort时间,数据重新排序,造成后面更新数据的更新错误. 解决方法: 方法一.设置Datagridview的表头 ...

随机推荐

  1. [学习笔记]设计模式之Composite

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在Composite(组合)模式中,用户可以使用多个简单的组件以形成较大的组件,而这些组件还可能进一步组合成更大的.它重要的特性是能够 ...

  2. JavaScript拖拽

    <!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-e ...

  3. jQuery实现商品楼层的感觉

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. WinForm控件复杂数据绑定常用数据源(对Combobox,DataGridView等控件DataSource赋值的多种方法)

    开始以前,先认识一下WinForm控件数据绑定的两种形式,简单数据绑定和复杂数据绑定. 1) 简单数据绑定 简单的数据绑定是将用户控件的某一个属性绑定至某一个类型实例上的某一属性.采用如下形式进行绑定 ...

  5. 10个必看的PHP小代码,很实用!

    获取浏览器IP地址 function getRemoteIPAddress() { $ip = $_SERVER['REMOTE_ADDR']; return $ip; } 如果有代理服务器的情况下获 ...

  6. Hibernate报错 ** is not mapping

    使用easyui+struts+hibernate 新增加一个页面功能时,总是报错,后来发现是数据库语句,不能写表名称,而是要写映射的数据库实体类名 1.struts文件修改增加action < ...

  7. [Linux]命令root与other切换

    切换至root:sudo -i切换至other:su 用户名 

  8. WPF Navigation导航

    WPF导航这个话题,网上的解决方法有很多种,有点吃猪脚的感觉,弃之可惜,食之乏味. 不过还是简单聊聊吧. 常见的导航: 利用HyperLink导航,可以到某一个Page页面,也可以是外部链接,当然也可 ...

  9. 最常用的动态sql语句梳理——分享给使用Mybatis的小伙伴们!

    公司项目中一直使用Mybatis作为持久层框架,自然,动态sql写得也比较多了,最常见的莫过于在查询语句中使用if标签来动态地改变过滤条件了.Mybatis的强大特性之一便是它的动态sql,免除了拼接 ...

  10. HDU 3507 Print Article 斜率优化

    Print Article Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)To ...