操作方法:管理跨线程 WinForms 控件访问
使用泛型和扩展方法提供易于使用的跨线程 WinForms 控件操作辅助工具。
引言
在开发复杂的业务应用程序时,开发人员经常需要处理在单独的线程上操作数据和 UI,因此必须管理跨线程控件访问。
在 WinForms 中,我们通过使用回调方法并在主线程上调用它们来实现这一点。
例如,考虑一个繁重的后台任务,它在计算机中搜索一些文件并在过程中发送回日志信息。我们应该实现一个处理事件的方法
private void AppendLogText(string logText)
{
if (tbLog.InvokeRequired)
{
AppendLogTextCallBack callBack = new AppendLogTextCallBack(AppendLogText);
Invoke(callBack, new object[] { logText });
}
else
{
if (!string.IsNullOrEmpty(tbLog.Text))
tbLog.AppendText(Environment.NewLine);
tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
}
}
本文将展示如何实现方法来简化跨线程控件访问的实现。
背景
在本文中,我们使用 TPL(任务并行库)异步运行工作线程。TPL 是框架 4 中包含的一组 API,请访问此链接了解更多信息:此处。
我们还使用 Action
类,它封装了一个方法。在这种情况下,它非常有用,可以封装回调方法,而无需将它们声明为类字段。要了解更多信息,请访问此链接:此处。
由于工作线程正在异步运行,因此工作线程实现了一些事件以发送回信息。
第一步:创建一个管理 invoke 方法的辅助工具
所有指南的基础是不重复开发过程:当您需要在代码中管理多个控件的跨线程访问时,应该只实现一次该模式。
为此,我们使用一个辅助工具,它提供了一种管理控件访问和回调方法触发的方法
public static class ControlThreadingHelper
{
public static void InvokeControlAction<t>(t control, Action action) where t : Control
{
if (control.InvokeRequired)
control.Invoke(new Action<t, Action>(InvokeControlAction), new object[] { control, action });
else
action();
}
}
由于泛型,此辅助工具可完全用于所有控件:它检查 InvokeRequired
属性并触发嵌入在 Action
中的回调方法。Action
类可以由 lambda 表达式赋值,因此编写代码非常简单。
有了这个辅助工具,我们现在可以通过一种独特的方式管理所有控件访问,无论是否跨线程
private void AppendLogText(string logText)
{
ControlThreadingHelper.InvokeControlAction(tbLog, () =>
{
if (!string.IsNullOrEmpty(tbLog.Text))
tbLog.AppendText(Environment.NewLine);
tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
});
}
第二步:简化代码编写
为了简化代码编写,我们应该直接在 Control
类中实现控件访问方法。
我们可以通过扩展方法来实现这一点:
public static class ControlThreadingExtensions
{
public static void Invoke<t>(this t control, Action action) where t : Control
{
ControlThreadingHelper.InvokeControlAction<t>(control, action);
}
}
此扩展方法允许我们将控件访问直接实现到 WinForm 的控件上
private void AppendLogText(string logText)
{
tbLog.Invoke(() =>
{
if (!string.IsNullOrEmpty(tbLog.Text))
tbLog.AppendText(Environment.NewLine);
tbLog.AppendText(string.Concat(DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"), " : ", logText));
});
}
我们已经分离了关注点:跨线程访问由辅助工具中的唯一方法管理,而特定的表单行为被分配到表单的后台代码中。
值得关注的点
在本文中,我们已经看到如何将 Action
类、辅助工具和扩展方法结合起来,帮助我们编写提供易于使用的跨线程控件访问方法。
历史
- 2012/06/19:Bug 修复:使用空 Action 代替 Action<t> 作为回调方法
- 2012/06/19:原始版本。