.NET一个线程更新另一个线程的UI(两种实现方法及若干简化)
本片博文接上一篇:.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 {
System.Threading.Thread t;
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() }); } } } private void Form1_Load(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(PrintTime)); t.Start(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (t != null) { t.Abort(); } }
}
}
比较和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(); } } }
程序的运行结果如下:
更新另一个线程的进度条示例(第一种方法实现)
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; //更新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 = 0; long num = Convert.ToInt64(o); UpdateProgressBarDelegate upd = new UpdateProgressBarDelegate(UpdateProgressBar); for (long i = 1; i <= num; i++) { result += i; //更新ProcessBar1 if (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); } } } //更新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()程序也可以正常运行,但是这样DebugLZQ不推荐这样,因为多线程调用方法参数不够清晰,可参考DebugLZQ关于多线程执行函数的博文。
根据个人编码习惯,可选择合适的编码方法。以上代码由DebugLZQ编写,可正常运行,就不附上界面截图了。
若是WPF程序,则使用:
Dispatcher.BeginInvoke(new Action(() => { }));
希望对你有帮助~