一、概述
在Winform界面程序中,系统创建了UI线程启动Form窗体,而我们创建其他的线程用于处理逻辑,当这些逻辑线程涉及到操作UI线程中的控件对象时则应考虑避免“线程安全”等问题。
错误示例:
private Thread LogicThread; // 逻辑线程
private void Form1_Load(object sender, EventArgs e)
{
// 线程创建
LogicThread = new Thread(new ThreadStart(LogicThreadMain));
LogicThread.Start();
}
/// <summary>
/// 线程方法
/// </summary>
private void LogicThreadMain()
{
// 错误示例
// 有可能报出Exception
textBox1.Text = "Error";
}
以下介绍两种解决方案:
- Invoke和BeginInvoke
- SynchronizationContext
二、Invoke和BeginInvoke
.NET的控件(Control)提供了Invoke和BeginInvoke两种委托用于将控件对象的处理操作加入消息队列。
- Invoke为同步委托,调用线程需要等待控件对象处理完后才可以继续执行。
- BeginInvoke为异步委托,调用线程向控件对象发送了处理操作后即继续执行,无需等待。
private Thread LogicThread; // 逻辑线程
private void Form1_Load(object sender, EventArgs e)
{
// 线程创建
LogicThread = new Thread(new ThreadStart(LogicThreadMain));
LogicThread.Start();
}
/// <summary>
/// 线程方法
/// </summary>
private void LogicThreadMain()
{
// 异步处理
this.BeginInvoke((MethodInvoker)(() => { textBox1.Text = "BeginInvoke"; }));
// 其他处理
Thread.Sleep(1000);
// 同步处理
this.Invoke((MethodInvoker)(() => { textBox1.Text = "Invoke"; }));
}
三、SynchronizationContext
.NET的Threading中提供了SynchronizationContext类实现一个线程到另一个线程的同步上下文通信。
SynchronizationContext提供了Send(SendOrPostCallback, object)和Post(SendOrPostCallback, object)两个通知方法将消息传播到目标的同步上下文。
- Send(SendOrPostCallback, object)为同步方法,调用线程需等待该方法执行完毕才能继续执行。
- Post(SendOrPostCallback, object)为异步方法,调用线程不需要等待执行完毕即继续执行。
private SynchronizationContext context; // 同步上下文
private Thread LogicThread; // 逻辑线程
private void Form1_Load(object sender, EventArgs e)
{
// 获取UI线程的同步上下文
context = SynchronizationContext.Current;
// 线程创建
LogicThread = new Thread(new ThreadStart(LogicThreadMain));
LogicThread.Start();
}
/// <summary>
/// 线程方法
/// </summary>
private void LogicThreadMain()
{
// 异步处理
context.Post(ShowText,"Post");
// 其他处理
Thread.Sleep(1000);
// 同步处理
context.Send(ShowText, "Send");
}
/// <summary>
/// 在UI线程的同步上下文委托方法
/// </summary>
/// <param name="obj"></param>
private void ShowText(object obj)
{
textBox1.Text = obj.ToString();
}
四、异步委托
基于上述两种方法,可以实现跨线程的UI控件操作与通信,实际应用中,异步实现UI更新的操作占多数,为此,为减少代码量,和大家分享一种我常用的基于异步委托BeginInvoke的解决方案。
首先,创建一个静态类,写好常用控件的异步委托操作方法,如下:
public static class BaseFunction
{
public static void ResetGridColor(this DataGridView grid)
{
foreach (DataGridViewColumn col in grid.Columns)
{
if (col.DefaultCellStyle.BackColor != Color.Empty)
col.DefaultCellStyle.BackColor = Color.Empty;
}
}
public static void UpDownValue(this NumericUpDown ob, decimal Value)
{
if (ob.InvokeRequired)
{
ob.BeginInvoke((MethodInvoker)(() =>
ob.UpDownValue(Value)
));
}
else
{
ob.Value = Value;
}
}
public static void SetText(this Object ob, string sText)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetText(sText)
));
}
else
{
((Control)ob).Text = sText;
}
}
}
public static void SetBackColor(this Object ob, Color cl)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetBackColor(cl)
));
}
else
{
((Control)ob).BackColor = cl;
}
}
}
public static void SetForeColor(this Object ob, Color cl)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetForeColor(cl)
));
}
else
{
((Control)ob).ForeColor = cl;
}
}
}
public static void SetFocus(this Object ob)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetFocus()
));
}
else
{
((Control)ob).Focus();
}
}
}
public static void SetEnable(this Object ob, bool bEnable)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetEnable(bEnable)
));
}
else
{
((Control)ob).Enabled = bEnable;
}
}
}
public static void SetVisible(this Object ob, bool bEnable)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetVisible(bEnable)
));
}
else
{
((Control)ob).Visible = bEnable;
}
}
}
public static void SetBackGroundImage(this Object ob, Image img)
{
Control cntl_OB = ((Control)ob);
if (cntl_OB != null)
{
if (cntl_OB.InvokeRequired)
{
cntl_OB.BeginInvoke((MethodInvoker)(() =>
ob.SetBackGroundImage(img)
));
}
else
{
((Control)ob).BackgroundImage = img;
}
}
}
public static void LayoutControlAdd(this TableLayoutPanel tb, Control Value, int column = 0, int row = 0)
{
if (tb.InvokeRequired)
{
tb.BeginInvoke((MethodInvoker)(() =>
tb.LayoutControlAdd(Value, column, row)
));
}
else
{
tb.Controls.Add(Value, column, row);
}
}
public static void ControlAdd(this TabControl tb, Control Value)
{
if (tb.InvokeRequired)
{
tb.BeginInvoke((MethodInvoker)(() =>
ControlAdd(tb, Value)
));
}
else
{
tb.Controls.Add(Value);
}
}
public static void ControlAdd(this Panel tb, Control Value)
{
if (tb.InvokeRequired)
{
tb.BeginInvoke((MethodInvoker)(() =>
ControlAdd(tb, Value)
));
}
else
{
tb.Controls.Add(Value);
}
}
public static void ControlAdd(this FlowLayoutPanel tb, Control Value)
{
if (tb.InvokeRequired)
{
tb.BeginInvoke((MethodInvoker)(() =>
ControlAdd(tb, Value)
));
}
else
{
tb.Controls.Add(Value);
}
}
public static void ControlAdd(this GroupBox tb, Control Value)
{
if (tb.InvokeRequired)
{
tb.BeginInvoke((MethodInvoker)(() =>
ControlAdd(tb, Value)
));
}
else
{
tb.Controls.Add(Value);
}
}
public static void ProgressBar_SetValue(this ProgressBar pg, int Value)
{
if (pg.InvokeRequired)
{
pg.BeginInvoke((MethodInvoker)(() =>
pg.ProgressBar_SetValue(Value)
));
}
else
{
pg.Value = Value > 100 ? 100 : Value;
}
}
public static void ComboBox_AddItem(this ComboBox cb, string sText)
{
if (sText != null)
if (cb.InvokeRequired)
{
cb.BeginInvoke((MethodInvoker)(() =>
cb.ComboBox_AddItem(sText)
));
}
else
{
cb.Items.Add(sText);
}
}
public static void ComboBox_AddItems(this ComboBox cb, List<string> aText)
{
if (aText != null && aText.Count > 0)
if (cb.InvokeRequired)
{
cb.BeginInvoke((MethodInvoker)(() =>
cb.ComboBox_AddItems(aText)
));
}
else
{
cb.Items.AddRange(aText.ToArray());
}
}
public static void ComboBox_Clear(this ComboBox cb)
{
if (cb.InvokeRequired)
{
cb.BeginInvoke((MethodInvoker)(() =>
cb.ComboBox_Clear()
));
}
else
{
cb.Items.Clear();
}
}
public static void ComboBox_SetSelectedIndex(this ComboBox cb, int index)
{
if (cb.InvokeRequired)
{
cb.BeginInvoke((MethodInvoker)(() =>
cb.ComboBox_SetSelectedIndex(index)
));
}
else
{
cb.SelectedIndex = index;
}
}
public static void ComboBox_SetSelectedItem(this ComboBox cb, Object ob)
{
if (cb.InvokeRequired)
{
cb.BeginInvoke((MethodInvoker)(() =>
cb.ComboBox_SetSelectedItem(ob)
));
}
else
{
cb.SelectedItem = ob;
}
}
public static void ClickVirtual(this Button bt)
{
if (bt.InvokeRequired)
{
bt.BeginInvoke((MethodInvoker)(() =>
bt.ClickVirtual()
));
}
else
{
bt.PerformClick();
}
}
public static void ClickVirtual(this RadioButton rd)
{
if (rd.InvokeRequired)
{
rd.BeginInvoke((MethodInvoker)(() =>
rd.ClickVirtual()
));
}
else
{
rd.PerformClick();
}
}
public static void NumericUpDown_SetValue(this NumericUpDown nud, decimal Value)
{
if (nud.InvokeRequired)
{
nud.BeginInvoke((MethodInvoker)(() =>
nud.NumericUpDown_SetValue(Value)
));
}
else
{
nud.Value = Value;
}
}
}
之后,对应的控件就会多出一个上面自定义的静态方法,即可在任何线程上直接调用,实现异步操作,大大减少了代码量,提高代码易读性。
private Thread LogicThread; // 逻辑线程
private void Form1_Load(object sender, EventArgs e)
{
// 线程创建
LogicThread = new Thread(new ThreadStart(LogicThreadMain));
LogicThread.Start();
}
/// <summary>
/// 线程方法
/// </summary>
private void LogicThreadMain()
{
// 异步处理
textBox1.SetText("Test");
}