多线程辅助类,可在线程忙时访问其属性和方法
? Conmajia 2012-2023
首发:2012.08.05 00:44:45
Miscellaneous Series: 128.1
多线程(multithreading)是一种充分利用中央处理器运行切片以提高程序运行效率和性能的常用技术。通常,高性能应用采取前后台方式,即将高负荷计算交由后台线程计算,而前台线程——一般是图形用户界面(GUI)——负责刷新显示计算结果。
在 .NET Framework 中,出于线程安全考虑,默认设定下不允许跨线程访问 GUI 属性和方法。跨线程的访问均会触发例如图 1 所示的 InvalidOperationException
无效操作异常。
尽管可以通过设置 Control.CheckForIllegalCrossThreadCalls
为 false
禁用此检查实现跨线程访问,但这将带来严重的线程安全隐患。
以 GUI 线程为例,更安全的跨线程访问技巧通常利用 Control.InvokeRequired
属性和 Control.Invoke
方法实现。典型的代码如下。
public void DoWork() {
if (control1.InvokeRequired) {
control1.Invoke(DoWork);
} else {
// work code
}
}
为了便于使用,以下给出一个名为 InvokeHelper
的多线程辅助类,用于较为简洁地实现跨线程访问主线程属性、方法。
InvokeHelper
有效代码约 150 行,主要方法为:
InvokeHelper.Invoke
调用主线程某个方法并返回该方法执行结果。
InvokeHelper.Invoke(<控件>, "<方法名>", <参数>);
InvokeHelper.Get
返回主线程某个属性值。
InvokeHelper.Get(<控件>, "<属性名>");
InvokeHelper.Set
设置主线程某个属性值。
InvokeHelper.Set(<控件>, "<属性名>", <属性值>);
完整的 InvokeHelper
类实现源代码如下。
/*******************************************************************************
* InvokeHelper.cs
* A thread-safe control invoker helper class.
* -----------------------------------------------------------------------------
* Project:Conmajia.Controls
* Author:Conmajia
* History:
* 4th Aug., 2012
* Added support for "Non-control" controls (such as ToolStripItem).
* 4th Aug., 2012
* Initiated.
******************************************************************************/
// A thread-safe control invoker helper class.
public class InvokeHelper {
private delegate object MethodInvoker(
Control control, string methodName, params object[] args);
private delegate object PropertyGetInvoker(
Control control, object noncontrol, string propertyName);
private delegate void PropertySetInvoker(
Control control, object noncontrol, string propertyName, object value);
private static PropertyInfo GetPropertyInfo(
Control control, object noncontrol, string propertyName) {
if (control != null && !string.IsNullOrEmpty(propertyName)) {
PropertyInfo pi = null;
Type t = null;
if (noncontrol != null)
t = noncontrol.GetType();
else
t = control.GetType();
pi = t.GetProperty(propertyName);
if (pi == null)
throw new InvalidOperationException(
string.Format(
"Can't find property {0} in {1}.",
propertyName, t.ToString()));
return pi;
} else
throw new ArgumentNullException("Invalid argument.");
}
// Invoke a method
public static object Invoke(Control control, string methodName, params object[] args) {
if (control != null && !string.IsNullOrEmpty(methodName))
if (control.InvokeRequired)
return control.Invoke(
new MethodInvoker(Invoke),
control, methodName, args);
else {
MethodInfo mi = null;
if (args != null && args.Length > 0) {
Type[] types = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
if (args[i] != null)
types[i] = args[i].GetType();
mi = control.GetType().GetMethod(methodName, types);
} else
mi = control.GetType().GetMethod(methodName);
// check method info you get
if (mi != null)
return mi.Invoke(control, args);
else
throw new InvalidOperationException("Invalid method.");
}
else
throw new ArgumentNullException("Invalid argument.");
}
// Get the value of a property
public static object Get(Control control, string propertyName) {
return Get(control, null, propertyName);
}
public static object Get(Control control, object noncontrol, string propertyName) {
if (control != null && !string.IsNullOrEmpty(propertyName))
if (control.InvokeRequired)
return control.Invoke(new PropertyGetInvoker(Get),
control,
noncontrol,
propertyName
);
else {
PropertyInfo pi = GetPropertyInfo(control, noncontrol, propertyName);
object invokee = (noncontrol == null) ? control : noncontrol;
if (pi != null)
if (pi.CanRead)
return pi.GetValue(invokee, null);
else
throw new FieldAccessException(
string.Format(
"{0}.{1} is a write-only property.",
invokee.GetType().ToString(),
propertyName
));
return null;
}
else
throw new ArgumentNullException("Invalid argument.");
}
// Set the value of a property
public static void Set(Control control, string propertyName, object value) {
Set(control, null, propertyName, value);
}
public static void Set(Control control, object noncontrol, string propertyName, object value) {
if (control != null && !string.IsNullOrEmpty(propertyName))
if (control.InvokeRequired)
control.Invoke(new PropertySetInvoker(Set),
control,
noncontrol,
propertyName,
value
);
else {
PropertyInfo pi = GetPropertyInfo(control, noncontrol, propertyName);
object invokee = (noncontrol == null) ? control : noncontrol;
if (pi != null)
if (pi.CanWrite)
pi.SetValue(invokee, value, null);
else
throw new FieldAccessException(
string.Format(
"{0}.{1} is a read-only property.",
invokee.GetType().ToString(),
propertyName
));
}
else
throw new ArgumentNullException("Invalid argument.");
}
}
借助多线程辅助类 InvokeHelper,用户能够实现在某线程执行阻塞式指令无法响应其他指令情况下,GUI 线程仍可以访问其中的属性和方法以更新用户界面。图 2 的动画演示了这一过程。尽管工作线程处于阻塞状态,但 GUI 线程不受影响且仍可访问该线程属性。
其中工作线程运行的阻塞代码如下。
// Blocking
int count = 0;
while(true) {
// ...
Thread.Sleep(1000);
count++;
}
参考文献
[1] Sergiu Josan, Making controls thread-safely, May 2009.
[2] vicoB, Extension of safeInvoke, July 2010.
? Conmajia 2012
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!