事实上,本文内容很简单且浅显,所以取消前戏,直接开始。。
源代码:在本文最后
这里是一张动画,演示在多线程(无限循环+Thread.Sleep)情况下主界面操作不受影响。
多线程是一种提高程序运行效率和性能的常用技术。随着我们学习工作的深入,在编程中或多或少会涉及到需要多线程的情况。多数时候,我们的操作模式是后台线程中处理数据,计算结果,然后在前台界面(GUI)中更新显示。
在.NET Framework中,为了保证线程安全,避免出现访问竞争等问题,是不允许跨线程访问窗体控件的。如果强行访问,则会引发InvalidOperationException无效操作异常,如下图:
为了实现跨线程访问控件,.NET Framework为每个控件提供了InvokeRequired属性和Invoke方法。使用这些技巧,就可以实现我们在其他线程中直接修改界面的需要。看起来似乎很简单,但实际每次调用都有不少代码需要编写,还需要自行处理各种异常。下面是典型的调用例子:
1
2
3
4
5
6
7
8
9
10
11
|
public
void
DoWork()
{
if
(control.InvokeRequired)
{
control.Invoke(DoWork);
}
else
{
// do work
}
}
|
为了便于使用,我封装了实现细节,在这里给出一个InvokeHelper类,使用该类即可方便地实现跨线程调用主界面控件方法、获取/设置控件属性等功能。
该类实现非常简单,有效代码约150行,主要有以下3个方法:
1.Invoke
该方法可以调用主界面控件的某个方法,并返回方法执行结果。用法如下:
1
|
InvokeHelper.Invoke(<控件>,
"<方法名称>"
, <参数>);
|
其中“参数”为参数列表,支持0个或多个参数。
2.Get
该方法可以获取主界面控件的某个属性。用法如下:
1
|
InvokeHelper.Get(<控件>,
"<属性名称>"
);
|
3.Set
该方法可以设置主界面控件的某个属性。用法如下:
1
|
InvokeHelper.Set(<控件>,
"<属性名称>"
, <属性值>);
|
下面是整个类的实现代码。最后是一个演示用的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
/*******************************************************************************
* InvokeHelper.cs
* A thread-safe control invoker helper class.
* -----------------------------------------------------------------------------
* Project:Conmajia.Controls
* Author:Conmajia
* Url:[email protected]
* History:
* 4th Aug., 2012
* Added support for "Non-control" controls (such as ToolStripItem).
*
* 4th Aug., 2012
* Initiated.
******************************************************************************/
using
System;
using
System.Collections.Generic;
using
System.Reflection;
using
System.Text;
using
System.Windows.Forms;
namespace
InvokerHelperDemo
{
/// <summary>
/// A thread-safe control invoker helper class.
/// </summary>
public
class
InvokeHelper
{
#region delegates
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);
#endregion
#region
static
methods
// helpers
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."
);
}
// outlines
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."
);
}
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."
);
}
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."
);
}
#endregion
}
}
|
下面是一个演示用的例子。在该例子中,创建了一个永久循环的线程,该线程每隔500毫秒修改一次界面显示。主要代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
Thread t;
private
void
button1_Click(
object
sender, EventArgs e)
{
if
(t ==
null
)
{
t =
new
Thread(multithread);
t.Start();
label4.Text =
string
.Format(
"Thread state:\n{0}"
,
t.ThreadState.ToString()
);
}
}
public
void
DoWork(
string
msg)
{
this
.label3.Text =
string
.Format(
"Invoke method: {0}"
, msg);
}
int
count = 0;
void
multithread()
{
while
(
true
)
{
InvokeHelper.Set(
this
.label1,
"Text"
,
string
.Format(
"Set value: {0}"
, count));
InvokeHelper.Set(
this
.label1,
"Tag"
, count);
string
value = InvokeHelper.Get(
this
.label1,
"Tag"
).ToString();
InvokeHelper.Set(
this
.label2,
"Text"
,
string
.Format(
"Get value: {0}"
, value));
InvokeHelper.Invoke(
this
,
"DoWork"
, value);
Thread.Sleep(500);
count++;
}
}
|
详细代码请参阅源代码。运行后效果正常,尽管线程t是无限循环的线程,但主界面并不受其阻塞,操作一切正常。
源代码:点击下载