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

一、前言

在单线程中设置窗体某个控件的值很简单的事,只需要设置控件文本的值就可以了,但是有的业务场景很是复杂,界面上的控件也很多,这种情况下当数据量比较多的时候,在单线程中更新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
16 void SetOrgValue(object obj)
17 {
18     this.Org.Text = obj.ToString();
19 }

这段代码显然是为了应付线程间操作无效的错误,关闭了跨线程访问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下使用;而BackgroundWorker和SynchronizationContext这两种方式是适用于Winform/Silverlight的。以前在写WinForm的时候,只知道采用委托的方式和BackgroundWorker来解决跨线程访问控件的问题,现在回头看看这块的内容,又知道了几种解决问题的方式,也许这就是孔子老人家所说的“温故而知新”吧。马上就要过年了,提前祝大家新年快乐!

作者:悠扬的牧笛

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

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

时间: 2024-11-13 04:37:06

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

Android子线程更新UI主线程方法之Handler

背景: 我们开发应用程序的时候,处于线程安全的原因子线程通常是不能直接更新主线程(UI线程)中的UI元素的,那么在Android开发中有几种方法解决这个问题,其中方法之一就是利用Handler处理的. 下面说下有关Handler相关的知识. 多线程一些基础知识回顾:在介绍Handler类相关知识之前,我们先看看在Java中是如何创建多线程的方法有两种:通过继承Thread类,重写Run方法来实现通过继承接口Runnable实现多线程 具体两者的区别与实现,看看这篇文章中的介绍:http://de

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

有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例: ????public partial class MainWindow : Window????{????????public MainWindow()????????{????????????InitializeComponent();????????????this.Dispatcher.Invoke(new Action(()=> { }));????????????this.Loaded

Android多线程更新UI的方式

Android下,对于耗时的操作要放到子线程中,要不然会残生ANR,本次我们就来学习一下Android多线程更新UI的方式. 首先我们来认识一下anr: anr:application not reponse:应用程序无响应 主线程:UI线程 anr产生的原因:主线程需要做很多重要的事情,响应点击事件,更新ui,如果在主线程里面阻塞时间过久,应用程序就会无响应,为了避免应用程序出现anr,所有的耗时的操作,都应该放在子线程中执行. 认识了anr后,我们就来学习一下怎样在Android下开启多线程

android异步更新UI界面的方法

在android平台下,进行多线程编程时,经常需要在主线程之外的一个单独的线程中进行某些处理,然后更新用户界面显示.但是,在主线线程之外的线程中直接更新页面显示的问题是:系统会报这个异常,android.view.viewroot$calledfromwrongthreadexception: only the original thread that created a view hierarchy can touch its views. (只有原始创建这个视图层次(view hierach

基础篇-在非UI线程中更新UI元素

个人原创,转载请注明出处: http://blog.csdn.net/supluo/article/details/ 先了解两个概念 1.UI:User Interface的缩写,用户界面的意思.你可以不恰当的理解为我们能够看到的,操作的东西:在Android中什么才称为UI呢,可以简单的理解为View及其子类等元素.这是一个不够正确的概念,只是对新手做一个简单的抛砖引玉. 2.ANR:Application Not Responding,意思是程序没有响应. 在如下情况下,Android会报出

c#多线程更新窗口(winform)GUI的数据

1. 在.net framwork 2.0中,可以通过以下代码来实现: 1 2 3 4 5 6 7 8 9 10 11 12 private delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue); public static void SetControlPropertyThreadSafe(Control control, st

黑马程序员——java基础——多线程

 黑马程序员--java基础--多线程 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 进程:是一个正在执行中的程序.每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元.线程在控制着进程的执行.一个进程中至少有一个线程. 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区.自己的变量.

Java基础-多线程-③多线程的同步之synchronized

使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程也对数据进行了操作,从而导致数据出错.由此我们想到一个解决的思路:将操作共享数据的代码行作为一个整体,同一时间只允许一个线程执行,执行过程中其他线程不能参与执行.线程同步就是用来实现这样的机制. synchronized代码块 Java中提供了synchronized关键字,将可能引发安全问题的代码

Java基础-多线程-②多线程的安全问题

什么是线程的安全问题? 上一篇 Java基础-多线程-①线程的创建和启动 我们说使用实现Runnable接口的方式来创建线程,可以实现多个线程共享资源: 1 class Dog implements Runnable { 2 // 定义线程共享数据 3 private int t = 100; 4 5 @Override 6 public void run() { 7 // TODO:死循环,暂不处理 8 while (true) { 9 if (t > 0) { 10 11 System.ou