本片博文接上一篇:.NET多线程执行函数,给出实现一个线程更新另一个线程UI的两种方法。

Winform中的控件是绑定到特定的线程的(一般是主线程),这意味着从另一个线程更新主线程的控件不能直接调用该控件的成员。

控件绑定到特定的线程这个概念如下:

为了从另一个线程更新主线程的Windows Form控件,可用的方法有:

首先用一个简单的程序来示例,这个程序的功能是:在Winfrom窗体上,通过多线程用label显示时间。给出下面的两种实现方式

1.结合使用特定控件的如下成员

InvokeRequired属性:返回一个bool值,指示调用者在不同的线程上调用控件时是否必须使用Invoke()方法。如果主调线程不是创建该控件的线程,或者还没有为控件创建窗口句柄,则返回true。

Invoke()方法:在拥有控件的底层窗口句柄的线程上执行委托。

BeginInvoke()方法:异步调用Invoke()方法。

EndInvoke()方法:获取BeginInvoke()方法启动的异步操作返回值。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;namespace 一个线程更新另一个线程UI2
{/// <summary>/// DebugLZQ/// http://www.cnblogs.com/DebugLZQ/// </summary>public partial class Form1 : Form{public Form1(){InitializeComponent();}private void UpdateLabel(Control ctrl, string s){ctrl.Text = s;}private delegate void UpdateLabelDelegate(Control ctrl, string s);private void PrintTime(){if (label1.InvokeRequired == true){UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);while(true){label1.Invoke(uld, new object[] { label1, DateTime.Now.ToString() });}}else{while (true){label1.Text = DateTime.Now.ToString();}}}private void Form1_Load(object sender, EventArgs e){//PrintTime();//错误的单线程调用
Thread t = new Thread(new ThreadStart(PrintTime));t.Start();}}
}

比较和BackgroundWorker控件方式的异同点。

2.使用BackgroundWorker控件。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;namespace 一个线程更新另一个线程UI
{/// <summary>/// DebugLZQ/// http://www.cnblogs.com/DebugLZQ/// </summary>public partial class Form1 : Form{public Form1(){InitializeComponent();}private void UpdateLabel(Control ctrl, string s){ctrl.Text = s;}private delegate void UpdateLabelDelegate(Control ctrl, string s);private void PrintTime(){if (label1.InvokeRequired == true){UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);while (true){label1.Invoke(uld, new object[] { label1, DateTime.Now.ToString() });}}else{while (true){label1.Text = DateTime.Now.ToString();}}}private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){PrintTime();}private void Form1_Load(object sender, EventArgs e){backgroundWorker1.RunWorkerAsync();}}
}

程序的运行结果如下:

--------------------

Update:请参考后续博文:WPF: Cancel an Command using BackgroundWorker

--------------------

更新另一个线程的进度条示例(第一种方法实现)

DebugLZQ觉得第一种方法要更直观一点,或是更容易理解一点。下面再用第一种方法来做一个Demo:输入一个数,多线程计算其和值更新界面上的Label,并用进度条显示计算的进度。实际上就是,更新另一个线程的两个UI控件。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;namespace 一个线程更新另一个线程的UI3
{/// <summary>/// DebugLZQ/// http://www.cnblogs.com/DebugLZQ/// </summary>public partial class Form1 : Form{public Form1(){InitializeComponent();}private static long result = 0;//更新Labelprivate void UpdateLabel(Control ctrl, string s){ctrl.Text = s;}private delegate void UpdateLabelDelegate(Control ctrl, string s);//更新ProgressBarprivate void UpdateProgressBar(ProgressBar ctrl, int n){ctrl.Value = n;}private delegate void UpdateProgressBarDelegate(ProgressBar ctrl, int n);private void Sum(object o){result = 0;long num = Convert.ToInt64(o);UpdateProgressBarDelegate upd = new UpdateProgressBarDelegate(UpdateProgressBar);for (long i = 1; i <= num; i++){result += i;//更新ProcessBar1if (i % 10000 == 0)//这个数值要选的合适,太小程序会卡死
                {if (progressBar1.InvokeRequired == true){progressBar1.Invoke(upd, new object[] { progressBar1, Convert.ToInt32((100 * i) / num) });//若是(i/num)*100,为什么进度条会卡滞?
                    }else{progressBar1.Value = Convert.ToInt32(i / num * 100);}                    }}            //更新lblResultif (lblResult.InvokeRequired == true){UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);lblResult.Invoke(uld, new object[] { lblResult, result.ToString() });}else{lblResult.Text = result.ToString();}            }private void btnStart_Click(object sender, EventArgs e){Thread t = new Thread(new ParameterizedThreadStart(Sum));t.Start(txtNum.Text);}}
}

程序的运行结果如下:

用BackgroundWorker控件可以实现相同的功能,个人觉得这样更容易理解~

第一种方法的若干简化

和异步方法调用一样,我们可以使用delegate、匿名方法、Action/Function等系统提供委托、Lambda表达式等进行简化。

如,第一种方法,更新界面时间,我们可以简化如下:

using System;
using System.Windows.Forms;
using System.Threading;namespace WindowsFormsApplication1
{public partial class FormActionFunction : Form{public FormActionFunction(){InitializeComponent();}private void UpdateLabel(){label1.Text = DateTime.Now.ToString();label2.Text = DateTime.Now.ToString();}private void PrintTime(){while (true){
                PrintTime(UpdateLabel);}}private void PrintTime(Action action){if (InvokeRequired){Invoke(action);}else{action();}}private void FormActionFunction_Load(object sender, EventArgs e){Thread t = new Thread(new ThreadStart(PrintTime));t.IsBackground = true;t.Start();}}
}

也可以再简化:

using System;
using System.Windows.Forms;
using System.Threading;namespace WindowsFormsApplication1
{public partial class FormActionFunction : Form{public FormActionFunction(){InitializeComponent();}      private void PrintTime(){while (true){PrintTime(() => { label1.Text = DateTime.Now.ToString(); label2.Text = DateTime.Now.ToString(); });//Lambda简写
            }}private void PrintTime(Action action){if (InvokeRequired){Invoke(action);}else{action();}}private void FormActionFunction_Load(object sender, EventArgs e){Thread t = new Thread(new ThreadStart(PrintTime));t.IsBackground = true;t.Start();}}
}

进一步简化:

using System;
using System.Windows.Forms;
using System.Threading;namespace WindowsFormsApplication1
{public partial class FormBestPractice : Form{public FormBestPractice(){InitializeComponent();}private void PrintTime(){while (true){Invoke(new Action(() => { label1.Text = DateTime.Now.ToString(); label2.Text = DateTime.Now.ToString(); }));}}private void FormBestPractice_Load(object sender, EventArgs e){Thread t = new Thread(new ThreadStart(PrintTime));t.IsBackground = true;t.Start();}}
}

再进一步简化:

using System;
using System.Windows.Forms;
using System.Threading;namespace WindowsFormsApplication1
{public partial class FormBestPractice2 : Form{public FormBestPractice2(){InitializeComponent();}     private void FormBestPractice2_Load(object sender, EventArgs e){Thread t = new Thread(new ThreadStart(() => {while (true){Invoke(new Action(() => { label1.Text = DateTime.Now.ToString(); label2.Text = DateTime.Now.ToString(); }));}            }));t.IsBackground = true;t.Start();}}
}

可根据代码风格要求,去掉  new ThreadStart()、new Action(),程序也可以正常运行,但是这样DebugLZQ不推荐这样,因为多线程调用方法参数不够清晰,可参考DebugLZQ关于多线程执行函数的博文。

根据个人编码习惯,可选择合适的编码方法。以上代码由DebugLZQ编写,可正常运行,就不附上界面截图了。

-----------------

说明:以上所有更新方法均默认为同步方法。

若需要异步执行,则把Invoke换成BeginInvoke即可,其优点是不会阻塞当前线程。

============================================

若是WPF程序,则只要把在Winform中用于更新拥有控件的线程使用的Invoke方法换成

Dispatcher.Invoke

即可。也给出一个Demo:

MainWindow.xaml, MainWindow.xaml.cs如下:

<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow" Height="350" Width="525"><Grid><StackPanel><Button Content="Start" Click="ButtonBase_OnClick" Height="40" Margin="40"/><Grid><ProgressBar x:Name="ProgressBar1" Height="60"/><TextBlock x:Name="TextBlock1" FontSize="40" HorizontalAlignment="Center" VerticalAlignment="Center"/></Grid></StackPanel></Grid>
</Window>

View Code

using System;
using System.Threading;
using System.Windows;namespace WpfApplication1
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void ButtonBase_OnClick(object sender, RoutedEventArgs e){ProgressBar1.Minimum = 0;ProgressBar1.Maximum = 100;Thread t = new Thread(() =>{for (int i = 0; i <= 100 * 1000; i++){Dispatcher.Invoke(() =>{if (i % 1000 == 0){ProgressBar1.Value = i / 1000;TextBlock1.Text = i/1000 + "%";}});}});t.IsBackground = true;t.Start();}}
}

View Code

效果如下:

若不想阻塞当前线程(注意:是当前线程,非UI线程,Dispatcher.BeginInvoke可能会阻塞UI线程,因为其做的事情是:将执行扔给UI线程去执行,立即返回当前线程~其不管UI线程死活),则可使用异步Invoke:

Dispatcher.BeginInvoke

.NET Framework 4.5 提供了新的异步模式Async,因此开发平台若为.NET 4.5,也可以使用:

Dispatcher.InvokeAsync

详细请参考DebugLZQ后续博文:WPF: Updating the UI from a non-UI thread

或是直接对同步方法进行异步封装,请参考DebugLZQ后续相关博文:从C#5.0说起:再次总结C#异步调用方法发展史。

小结

无论WPF还是WinForm,UI都是由单个线程负责更新~

Invoke解决的问题是:非UI线程无法更新UI线程的问题. 其实现方法是将方法扔给UI线程执行(To UI Thread),Invoke等待UI线程执行完成才返回;BeginInvoke不等待UI线程执行完立刻返回~

Invoke/BeginInvoke可能会阻塞UI线程.(Invoke/BeginInvoke的区别是:是否等待UI线程执行完才返回当前线程继续执行~)

不阻塞UI线程:其解决方法是将耗时的非更新UI的操作操作放到后台线程里去,与Invoke/BeginInvoke没有半毛钱关系~也就是:别给UI线程太大压力!

希望对你有帮助~

.NET一个线程更新另一个线程的UI(两种实现方法及若干简化)相关推荐

  1. mysql用一个表更新另一个表的方法

    Solution 1:  修改1列(navicate可行) update student s, city c set s.city_name = c.name where s.city_code = ...

  2. 通过ID查询一个用户的两种开发方法

    通过ID查询一个用户的两种开发方法 数据库建表sql语句如下:https://github.com/beyondyanyu/Sayingyy/blob/master/JDBC2-数据库sql建表语句 ...

  3. java编写一个彩票开奖的模拟程序.游戏共有两种玩法,一种是21选5,即玩家输入5个1到21内的不重复的数。另外一种玩法是6+1玩法,即要求玩家输入7个整数,代表所购买的彩票号码,最后一个是特码。

    java编写一个彩票开奖的模拟程序.游戏共有两种玩法,一种是21选5,即玩家输入5个1到21内的不重复的数.另外一种玩法是6+1玩法,即要求玩家输入7个整数,代表所购买的彩票号码,最后一个是特码. 具 ...

  4. 按要求编写一个Java应用程序:(1)定义一个类,描述一个矩形,包含有长、宽两种属性,和计算面积方法。(2)编写一个类,继承自矩形类,同时该类描述长方体,具有长、宽、高属性

    (1)定义一个类,描述一个矩形,包含有长.宽两种属性,和计算面积方法. public class Jvcs {private int Long; //长private int wide; //宽pub ...

  5. 定义一个类,描述一个矩形,包含有长、宽两种属性,和计算面积方法。

    题目: 按要求编写一个Java应用程序: (1)定义一个类,描述一个矩形,包含有长.宽两种属性,和计算面积方法. (2)编写一个类,继承自矩形类,同时该类描述长方体,具有长.宽.高属性, 和计算体积的 ...

  6. xp系统更新的服务器失败是怎么回事啊,xp系统显示“服务器错误500”的两种解决方法...

    xp纯净版系统在浏览网页的时候会遇到这样或者那样的错误,比如经常遇到404错误,503错误,怎么回事呢?这些都是HTTP的状态码,不同的状态码代表不同的错误类型,有些不常用的状态码便没有详细的记载,例 ...

  7. 配置计算机卡住了一直0,win10更新卡在0%怎么办_win10更新一直0%的两种解决方法...

    我们在操作win10电脑时为了电脑的安全稳定,常常会对系统进行更新,但最近有网友向小编反映说自己的win10精简版电脑更新卡在0%,很久都没有响应.那我们碰到这种情况要怎么解决呢?下面小编就为大家整理 ...

  8. mySQL:两表更新(用一个表更新另一个表)的SQL语句

    用一个表中的字段去更新另外一个表中的字段, MySQL 中有相应的 update 语句来支持,不过这个 update 语法有些特殊.看一个例子就明白了. create table student (s ...

  9. 搞定“另一个 OleDbParameterCollection 中已包含 OleDbParameter。”的两种办法。

    今天调试程序,遇到一个奇怪的异常. 程序非常简单,就是从一个表中取出一个符合要求的数据,如果取到,就把该数据对应的计数加1. 也就是执行不同的两个SQL语句操作同一个表,并且这两个SQL的参数是一样的 ...

最新文章

  1. 高防御服务器与高防御IP之间的关系
  2. 研究38位知名CEO的邮件后,我们有这9个发现
  3. java违反唯一约束异常_Caused by: java.sql.BatchUpdateException: ORA-00001: 违反唯一约束条件 (DSPACE.SYS_C007868)...
  4. python--re模块
  5. 内存中常见异常值的解释(0xcccccccc 0xcdcdcdcd 0xfeeefeee等)
  6. html如何加载ae做好的,AE转JS动画,lottie.js和bodymovin的简易使用心得
  7. 百度文库等禁止页面弹出(禁用javascript)
  8. java mis_关于使用java开发Mis系统的相关内容。
  9. 赚一辈子的钱,还是一辈子赚钱? 掌握下一个财富分配周期的法则
  10. 第三阶段应用层——1.7 数码相册—电子书(2)—编写通用的Makefile
  11. 房产圈的极客---前搜房网副CTO曹艳白干了件大事!
  12. Big-Endian 和 Little-Endian 模式的区别
  13. python 数字转换为汉字大写
  14. 企业如何做好数据防泄漏需求分析
  15. 回头再说--英雄 汪峰
  16. QC Camera 3A\ISP 常见缩写记录
  17. c语言赛车游戏代码大全,初学者天地游戏制作--赛车游戏的完整图
  18. 《MLB棒球创造营》:棒球团建·一球成名
  19. 诚之和:首个俄罗斯太空电影摄制组准备返回地球
  20. 生成SIN波形的一个小工具

热门文章

  1. Ubuntu18.04将软件(Eclipse)固定在侧边收藏夹
  2. libevent在windows下使用步骤详解
  3. nginx中的数组结构ngx_array_t
  4. 进程通信学习笔记(System V消息队列)
  5. IT人不要一辈子做技术
  6. windows下安装RabbitMQ
  7. Redis 5种数据结构
  8. Java集合及concurrent并发包总结(转)
  9. 【转/TCP协议编程】 基于TCP的Socket 编程
  10. 人生如戏,请给我好一点儿的演技