一、System.Windows.Forms.MethodInvoker 类型是一个系统定义的委托,用于调用不带参数的方法。
private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
myThread.Start();
}
private void RunsOnWorkerThread()
{
MethodInvoker mi = new MethodInvoker(SetControlsProp);
BeginInvoke(mi);
}
private void SetControlsProp()
{
label1.Text = "myThread线程调用UI控件";
}
二、直接用System.EventHandler(可带参数)
private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
myThread.Start();
}
private void RunsOnWorkerThread()
{
//DoSomethingSlow();
string pList = "myThread线程调用UI控件";
label1.BeginInvoke(new System.EventHandler(UpdateUI), pList);
}
//直接用System.EventHandler,没有必要自定义委托
private void UpdateUI(object o, System.EventArgs e)
{
//UI线程设置label1属性
label1.Text = o.ToString() + "成功!";
}
三、包装 Control.Invoke
虽然第二个方法中的代码解决了这个问题,但它相当繁琐。如果辅助线程希望在结束时提供更多的反馈信息,而不是简单地给出“Finished!”消 息,则 BeginInvoke 过于复杂的使用方法会令人生畏。为了传达其他消息,例如“正在处理”、“一切顺利”等等,需要设法向 UpdateUI 函数传递一个参数。可能还需要添加一个进度栏以提高反馈能力。这么多次调用 BeginInvoke 可能导致辅助线程受该代码支配。这样不仅会造成不便,而且考虑到辅助线程与 UI 的协调性,这样设计也不好。对这些进行分析之后,我们认为包装函数可以解决这两个问题。
private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
myThread.Start();
}
private void RunsOnWorkerThread()
{
DoSomethingSlow();
for (int i = 0; i < 100; i++)
{
ShowProgress( Convert.ToString(i)+"%", i);
Thread.Sleep(100);
}
}
public void ShowProgress(string msg, int percentDone)
{
// Wrap the parameters in some EventArgs-derived custom class:
System.EventArgs e = new MyProgressEvents(msg, percentDone);
object[] pList = { this, e };
BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);
}
private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e);
private void UpdateUI(object sender, MyProgressEvents e)
{
lblStatus.Text = e.Msg;
myProgressControl.Value = e.PercentDone;
}
public class MyProgressEvents : EventArgs
{
public string Msg;
public int PercentDone;
public MyProgressEvents(string msg, int per)
{
Msg = msg;
PercentDone = per;
}
}
ShowProgress 方法对将调用引向正确线程的工作进行封装。这意味着辅助线程代码不再担心需要过多关注 UI 细节,而只要定期调用 ShowProgress 即可。
如果我提供一个设计为可从任何线程调用的公共方法,则完全有可能某人会从 UI 线程调用这个方法。在这种情况下,没必要调用 BeginInvoke,因为我已经处于正确的线程中。调用 Invoke 完全是浪费时间和资源,不如直接调用适当的方法。为了避免这种情况,Control 类将公开一个称为 InvokeRequired 的属性。这是“只限 UI 线程”规则的另一个例外。它可从任何线程读取,如果调用线程是 UI 线程,则返回假,其他线程则返回真。这意味着我可以按以下方式修改包装:
public void ShowProgress(string msg, int percentDone)
{
if (InvokeRequired)
{
// As before
//...
}
else
{
// We're already on the UI thread just
// call straight through.
UpdateUI(this, new MyProgressEvents(msg,PercentDone));
}
}