C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)
概述
C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常。处理跨线程更新Winform UI控件常用的方法有4种:
1. 通过UI线程的SynchronizationContext的Post/Send方法更新;
2. 通过UI控件的Invoke/BeginInvoke方法更新;
3. 通过BackgroundWorker取代Thread执行异步操作;
4. 通过设置窗体属性,取消线程安全检查来避免"跨线程操作异常"(非线程安全,建议不使用)。
下文中对以上3种方法应用进行举例说明,希望能对初识C# Winform的同学们有些帮助。
成文表分享交流之意,惶恐水平有限,文中理解和表述有错误之处还请大家多被批评指正。
更新记录:
2018年2月3日,根据网友评论提示更新错别字,BegainInvoke=》BeginInvoke。
正文
1. 通过UI线程的SynchronizationContext的Post/Send方法更新
用法:
//共分三步
//第一步:获取UI线程同步上下文(在窗体构造函数或FormLoad事件中)
/// <summary>
/// UI线程的同步上下文
/// </summary>
SynchronizationContext m_SyncContext = null;
public Form1()
{
InitializeComponent();
//获取UI线程同步上下文
m_SyncContext = SynchronizationContext.Current;
//Control.CheckForIllegalCrossThreadCalls = false;
}
//第二步:定义线程的主体方法
/// <summary>
/// 线程的主体方法
/// </summary>
private void ThreadProcSafePost()
{
//...执行线程任务 //在线程中更新UI(通过UI线程同步上下文m_SyncContext)
m_SyncContext.Post(SetTextSafePost, "This text was set safely by SynchronizationContext-Post."); //...执行线程其他任务
}
//第三步:定义更新UI控件的方法
/// <summary>
/// 更新文本框内容的方法
/// </summary>
/// <param name="text"></param>
private void SetTextSafePost(object text)
{
this.textBox1.Text = text.ToString();
}
//之后,启动线程
/// <summary>
/// 启动线程按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void setSafePostBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafePost));
this.demoThread.Start();
}
说明:三处加粗部分是关键。该方法的主要原理是:在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。
在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。
//在线程中更新UI(通过UI线程同步上下文m_SyncContext)
m_SyncContext.Post(SetTextSafePost, "This text was set safely by SynchronizationContext-Post.");
可以这样解读这行代码:向UI线程的同步上下文(m_SyncContext)中提交一个异步消息(UI线程,你收到消息后以异步的方式执行委托,调用方法SetTextSafePost,参数是“this text was ....”).
2.通过UI控件的Invoke/BeginInvoke方法更新
用法:与方法1类似,可分为三个步骤。
// 共分三步
// 第一步:定义委托类型
// 将text更新的界面控件的委托类型
delegate void SetTextCallback(string text);
//第二步:定义线程的主体方法
/// <summary>
/// 线程的主体方法
/// </summary>
private void ThreadProcSafe()
{
//...执行线程任务
//在线程中更新UI(通过控件的.Invoke方法)
this.SetText("This text was set safely.");
//...执行线程其他任务
}
//第三步:定义更新UI控件的方法
/// <summary>
/// 更新文本框内容的方法
/// </summary>
/// <param name="text"></param>
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)//如果调用控件的线程和创建创建控件的线程不是同一个则为True
{
while (!this.textBox1.IsHandleCreated)
{
//解决窗体关闭时出现“访问已释放句柄“的异常
if (this.textBox1.Disposing || this.textBox1.IsDisposed)
return;
}
SetTextCallback d = new SetTextCallback(SetText);
this.textBox1.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
//之后,启动线程
/// <summary>
/// 启动线程按钮事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
说明:这个方法是目前跨线程更新UI使用的主流方法,使用控件的Invoke/BeginInvoke方法,将委托转到UI线程上调用,实现线程安全的更新。原理与方法1类似,本质上还是把线程中要提交的消息,通过控件句柄调用委托交到UI线程中去处理。
解决窗体关闭时出现“访问已释放句柄“的异常部分代码参考博客园-事理同学的文章。
3.通过BackgroundWorker取代Thread执行异步操作
用法:
//共分三步
//第一步:定义BackgroundWorker对象,并注册事件(执行线程主体、执行UI更新事件)
private BackgroundWorker backgroundWorker1 =null;
public Form1()
{
InitializeComponent();
backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
//设置报告进度更新
backgroundWorker1.WorkerReportsProgress = true;
//注册线程主体方法
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
//注册更新UI方法
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
//backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
}
//第二步:定义执行线程主体事件
//线程主体方法
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//...执行线程任务
//在线程中更新UI(通过ReportProgress方法)
backgroundWorker1.ReportProgress(50, "This text was set safely by BackgroundWorker.");
//...执行线程其他任务
}
//第三步:定义执行UI更新事件
//UI更新方法
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.textBox1.Text = e.UserState.ToString();
}
//之后,启动线程
//启动backgroundWorker
private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
说明:C# Winform中执行异步任务时,BackgroundWorker是个不错的选择。它是EAP(Event based Asynchronous Pattern)思想的产物,DoWork用来执行异步任务,在任务执行过程中/执行完成后,我们可以通过ProgressChanged,ProgressCompleteded事件进行线程安全的UI更新。
需要注意的是://设置报告进度更新
backgroundWorker1.WorkerReportsProgress = true;
默认情况下BackgroundWorker是不报告进度的,需要显示设置报告进度属性。
4. 通过设置窗体属性,取消线程安全检查来避免"线程间操作无效异常"(非线程安全,建议不使用)
用法:将Control类的静态属性CheckForIllegalCrossThreadCalls为false。
public Form1()
{
InitializeComponent();
//指定不再捕获对错误线程的调用
Control.CheckForIllegalCrossThreadCalls = false;
}
说明:通过设置CheckForIllegalCrossThreadCalls属性,可以指示是否捕获线程间非安全操作异常。该属性值默认为ture,即线程间非安全操作是要捕获异常的("线程间操作无效"异常)。通过设置该属性为false简单的屏蔽了该异常。Control.CheckForIllegalCrossThreadCalls的注释如下
//
// 摘要:
// 获取或设置一个值,该值指示是否捕获对错误线程的调用,这些调用在调试应用程序时访问控件的 System.Windows.Forms.Control.Handle
// 属性。
//
// 返回结果:
// 如果捕获了对错误线程的调用,则为 true;否则为 false。
[EditorBrowsable(EditorBrowsableState.Advanced)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription("ControlCheckForIllegalCrossThreadCalls")]
[Browsable(false)]
public static bool CheckForIllegalCrossThreadCalls { get; set; }
综述:
文中介绍的4种方法,前三种是线程安全的 ,可在实际项目中因地制宜的使用。最后一种方法是非线程安全的,初学者可以实验体会但不建议使用它。
下面列表对比一下这四种方法
| 方法 | 线程安全 | 支持异步/同步 | 其他 |
| UI SyncContext更新 | 是 | Post/Send | 尽量在窗体构造函数、FormLoad中获取同步上下文 |
| 控件Invoke | 是 | control.Invoke/BeginInvoke | 注意检查控件句柄是否已释放 |
| BackgroundWorker更新 | 是 |
ProgressChanged、RunWorkerCompleted 事件同步更新 |
报告进度 |
|
CheckForIllegalCrossThreadCalls 取消跨线程调用检查 |
否 | 同步更新 | 简单,不建议使用 |
参考资料:
瑞奇特(Jeffrey Richter),《clr var C#》
C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)的更多相关文章
- C# Winform 跨线程更新UI控件常用方法总结(转)
出处:http://www.tuicool.com/articles/FNzURb 概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件 ...
- C# Winform 跨线程更新UI控件常用方法汇总
https://www.cnblogs.com/marshal-m/p/3201051.html
- Winform之跨线程更新UI
Winform之跨线程更新UI 使用`Invoke`或者`BeginInvoke`与UI线程交互示例 参考及源码 使用Invoke或者BeginInvoke与UI线程交互示例 private void ...
- (转).NET 4.5中使用Task.Run和Parallel.For()实现的C# Winform多线程任务及跨线程更新UI控件综合实例
http://2sharings.com/2014/net-4-5-task-run-parallel-for-winform-cross-multiple-threads-update-ui-dem ...
- [WinForm]WinForm跨线程UI操作常用控件类大全
前言 在C#开发的WinForm窗体程序开发的时候,经常会使用多线程处理一些比较耗时之类的操作.不过会有一个问题:就是涉及到跨线程操作UI元素. 相信才开始接触的人一定会遇上这个问题. 为了解决这个问 ...
- Winfrom 如何安全简单的跨线程更新控件
来源:http://www.cnblogs.com/rainbowzc/archive/2010/09/29/1838788.html 由于多线程可能导致对控件访问的不一致,导致出现问题.C#中默认是 ...
- 简短总结一下C#里跨线程更新UI(转)
摘自: http://my.oschina.net/sdqxcxh/blog/53707 跨线程更新UI是写多线程程序尤其是通信类的程序经常遇到的问题,这里面主要的问题是冲突,比如数据线程想要更新UI ...
- 简短总结一下C#里跨线程更新UI
摘自: http://my.oschina.net/sdqxcxh/blog/53707 跨线程更新UI是写多线程程序尤其是通信类的程序经常遇到的问题,这里面主要的问题是冲突,比如数据线程想要更新UI ...
- C# 跨线程更新 UI
Winforms 跨线程更新 UI 在 Winforms 中, 所有的控件都包含 InvokeRequired 属性, 如果我们要更新UI,通过它我们可以判断是否需要调用 [Begin]Invoke. ...
随机推荐
- Linux shell脚本学习
1.cgmod +x ./文件 获取执行权限,使脚本具有执行权限 2. ./name.sh 执行脚本 ./为当前目录 直接使用name.sh系统会去PATH里面找name.sh,而PATH里只有/ ...
- java web(七): mybatis的动态sql和mybatis generator自动生成pojo类和映射文件
前言: MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据 不同条件拼接 SQL 语句的痛苦.例如拼接时要确保不能忘记添加必要的空格,还 ...
- PythonStudy——编程基础 Python Primary
1.什么是编程语言 语言: 一个事物与另外一个事物沟通的介质 .编程语言是程序员与计算机沟通的介质. 编程: 将人类内识别的语言转化为机器能识别的指令,这种过程就叫做编程. 注:最终这些指令会被转化 ...
- nomad 0.9 新特性
内容摘自github Affinities and Spread: Jobs may now specify affinities towards certain node attributes. A ...
- 第一个Python小项目:图片转换成字符图片
实现的效果: ...
- 微信支付,退款时,出现了内部错误-网站中X509Certificate2加载证书时出错
今天给阿里云,虚拟主机 网站配置了加密证书文件,用类X509Certificate2加载证书文件时,一直报出现了内部错误,但是Demo中用控制台程序加载证书没任何问题 读取证书文件的语句: X509C ...
- Java8 新特性学习
摘自:https://blog.csdn.net/shuaicihai/article/details/72615495 Lambda 表达式 Lambda 是一个匿名函数,我们可以把 Lambda ...
- Linux之正则表达式1
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑.适当使用正则表达式可以提高工作效 ...
- Java多态(非常重要)
多态(多种形态)的定义 同一消息对不同类的对象做出的不同响应 种类 在程序设计中一般说多态都是运行时多态 多态施行的条件: 1满足继承关系 2父类引用指向子类对象(向上转型) 向上转型 向下转型(子类 ...
- 为嵌入式mplayer移植添加ALSA音频驱动(全志V3s荔枝派zero)
首先准备mplayer和alsa_lib,我的是bulidroot添加后编译自动下载的,版本分别是alsa-lib-1.1.4.1和mplayer-1.3.0. 首先编译alsa_lib: ./con ...