.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 = ; //更新Label
private void UpdateLabel(Control ctrl, string s)
{
ctrl.Text = s;
} private delegate void UpdateLabelDelegate(Control ctrl, string s); //更新ProgressBar
private void UpdateProgressBar(ProgressBar ctrl, int n)
{
ctrl.Value = n;
} private delegate void UpdateProgressBarDelegate(ProgressBar ctrl, int n); private void Sum(object o)
{
result = ; long num = Convert.ToInt64(o); UpdateProgressBarDelegate upd = new UpdateProgressBarDelegate(UpdateProgressBar); for (long i = ; i <= num; i++)
{
result += i;
//更新ProcessBar1
if (i % == )//这个数值要选的合适,太小程序会卡死
{
if (progressBar1.InvokeRequired == true)
{
progressBar1.Invoke(upd, new object[] { progressBar1, Convert.ToInt32(( * i) / num) });//若是(i/num)*100,为什么进度条会卡滞?
}
else
{
progressBar1.Value = Convert.ToInt32(i / num * );
}
} } //更新lblResult
if (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="" Width="">
<Grid>
<StackPanel>
<Button Content="Start" Click="ButtonBase_OnClick" Height="" Margin=""/>
<Grid>
<ProgressBar x:Name="ProgressBar1" Height=""/>
<TextBlock x:Name="TextBlock1" FontSize="" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</Grid>
</Window>
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 = ;
ProgressBar1.Maximum = ; Thread t = new Thread(() =>
{
for (int i = ; i <= * ; i++)
{
Dispatcher.Invoke(() => {
if (i % == )
{
ProgressBar1.Value = i / ;
TextBlock1.Text = i/ + "%";
}
});
}
});
t.IsBackground = true;
t.Start();
}
}
}
若不想阻塞当前线程(注意:是当前线程,非UI线程,Dispatcher.BeginInvoke可能会阻塞UI线程,因为其做的事情是:将执行扔给UI线程去执行,立即返回当前线程~其不管UI线程死活),则可使用异步Invoke:
.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线程太大压力!
WPF异步加载图片:
前台代码就不说了,直接看后台代码
private void btn2_Click(object sender, RoutedEventArgs e)
{//一个按钮,点击后开始加载图片
Thread t = new Thread(new ThreadStart(showImg));
t.IsBackground = true;
t.Start(); }
private void showImg()
{
string path = @"C:\Users\Administrator\Pictures\Saved Pictures\@1J_WCD4}~OQ3M~V$U)V5IT.jpg";
BuildImg(g00, path);//g00是一个Image控件的名称
path = @"C:\Users\Administrator\Pictures\Saved Pictures\@7DU3EIZWG]P_R_8~[[3A@V.jpg";
BuildImg(g01, path);
path = @"C:\Users\Administrator\Pictures\Saved Pictures\@8(5GTF~7EHKOXO9B33$E}0.jpg";
BuildImg(g02, path);
path = @"C:\Users\Administrator\Pictures\Saved Pictures\@C(A)8MXU~{CFDSAPW[}]0E.jpg";
BuildImg(g11, path);
path = @"C:\Users\Administrator\Pictures\Saved Pictures\@MDSR`[_((G[JUZQ~RJ}DXP.jpg";
BuildImg(g12, path);
path = @"E:\XXX\YHYHYGY\MJNIJU.jpg";
BuildImg(g13, path);
}
private void BuildImg(Image img, string path)
{
img.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
BitmapImage bi3 = new BitmapImage();
bi3.BeginInit();
bi3.UriSource = new Uri(path, UriKind.RelativeOrAbsolute);
bi3.EndInit();
img.Source = bi3;
img.Stretch = Stretch.Fill;
}));
Thread.Sleep();//这里睡一秒是用来模仿加载图片这一耗时操作。靠,后来才知道把耗时操作设置在这里根本看不出效果,应该将这句放在bi3.EndInit()前面才行
}
上面的代码在下载远程图片的时候仍然会导致界面卡顿。BitmapImage很奇怪,在用户线程创建的这类对象,传递到主线程的时候总是报异常。
下面的这篇文章也是关于异步加载图片的,很有见解http://www.cnblogs.com/clowwindy/archive/2009/02/13/1390060.html
.NET一个线程更新另一个线程的UI(两种实现方法及若干简化)的更多相关文章
- mysql用一个表更新另一个表的方法
Solution 1: 修改1列(navicate可行) update student s, city c set s.city_name = c.name where s.city_code = ...
- Android—— 线程 thread 两种实现方法!(转)
原文地址:http://blog.csdn.net/boyupeng/article/details/6208072 这篇文章中有三点需要提前说明一下, 一: 在android中有两种实现线程thre ...
- 设置一个DIV块固定在屏幕中央(两种方法)
设置一个DIV块固定在屏幕中央(两种方法) 方法一: 对一个div进行以下设置即可实现居中. <style> #a{ position: fixed; top: 0px; left: 0p ...
- mySQL:两表更新(用一个表更新另一个表)的SQL语句
用一个表中的字段去更新另外一个表中的字段, MySQL 中有相应的 update 语句来支持,不过这个 update 语法有些特殊.看一个例子就明白了. create table student ( ...
- 【转载】JAVA中线程的两种实现方法-实现Runnable接口和继承Thread类
转自: http://blog.csdn.net/sunguangran/article/details/6069317 非常感谢原作者,整理的这么详细. 在java中可有两种方式实现多线程,一种是继 ...
- Android 线程 thread 两种实现方法
原文链接: http://blog.csdn.net/boyupeng/article/details/6208072 这篇文章中有三点需要提前说明一下, 一: 在android中有两种实现线程thr ...
- oracle 用一个表的一个字段更新另一个表的一个字段
案列: 想更新A表的name字段,由于失误,在写这个表的时候,这个字段没有写,发现的时候,已经写了一个多月的数据了.改了之后的过程,会正常的写这个字段, 可是已经写了的数据也不能铲了,重新计算. 好在 ...
- sql 根据一个表更新 另一个表的例子及可能遇到的问题
例子: update a set a.name=b.name1 from a,b where a.id=b.id 例子延伸:更新的时候会把字符串 转为科学计数法 怎么办? 答:用 cast 转换一下 ...
- oracle 根据一个表更新另一个表内容
declarecursor c_col is select * from xtgl_jgmcbm where substr(v_jgbm,0,2)in('41');--v_sjbm in( selec ...
随机推荐
- git学习笔记 (三)
学习地址 http://www.liaoxuefeng.com http://www.cnblogs.com/renkangke/archive/2013/05/31/conquerAndroid.h ...
- 使用react-native做一个简单的应用-05 navigator的使用
今天生病了,难受... Navigator顾名思义就是导航器的意思. 在Android和iOS中,导航器的样式是不一样的 ,有一个比较好的网址可以提供大家学习(网址),由于样式的不统一,于是我决定自己 ...
- html表格,列表
1. 表格由 <table> 标签来定义.每个表格均有若干行(由 <tr> 标签定义),每行被分割为若干单元格(由 <td> 标签定义).字母 td 指表格数据(t ...
- LP64是什么意思
在64位机器上,如果int是32位,long是64位,pointer也是64位,那么该机器就是LP64的,其中的L表示Long,P表示Pointer,64表示Long和Pointer都是64位的.由于 ...
- 修改mysql编码为UTF-8
mysql> show variables like '%character%'; +--------------------------+--------------------------- ...
- PHP ReflectionClass
<?php /** * @desc test reflectionclass * @author songweiqing * @create_time 2015-01-7 * */ class ...
- django获取某一个字段的列表,values/values_list/flat
class Building(models.Model): corporation = models.ForeignKey('company.Corporation', verbose_name=u' ...
- python socket 编程之二:tcp三次握手
建立起一个TCP连接需要经过“三次握手”:第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认:第二次握手:服务器收到syn包,必须确认客户的SYN(ack ...
- gcc编译通过,运行却显示“段错误”的解决方法
第一次在Liunx上(liunx mint 17)使用gcc编译c文件,竟然提示“找不到stdio.h",经过google后发现执行 sudo apt-get install build- ...
- java时间比较
public static void main(String[] args) throws Exception{//判断某个时间是否为当前时间 String dataStr="2014-07 ...