我想您的代码只是一个测试,所以我不会讨论您如何使用计时器。这里的问题是如何使用计时器回调中的用户界面控件执行某些操作。
的大多数Control
方法和属性只能从UI线程访问(实际上,只能从创建它们的线程访问它们,但这是另一回事了)。这是因为每个线程都必须有自己的消息循环(GetMessage()
按线程过滤出消息),然后再使用a做某事,因此Control
必须将消息从线程分派到主 线程。在.NET中很容易因为每一个Control
继承了几个用于此目的的方法:Invoke/BeginInvoke/EndInvoke
。要知道执行线程是否必须调用那些方法,您具有属性Invokerequired
。只需更改此代码即可使其起作用:
if (elapsedTime < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
}));
}
请检查MSDN对于您可以从任何线程调用的方法列表中,只是作为参考,你可以随时调用Invalidate
,BeginInvoke
,EndInvoke
,Invoke
方法和读取Invokerequired
性能。通常,这是一种常见的用法模式(假设this
是从派生的对象Control
):
void DoStuff() {
// Has been called from a "wrong" thread?
if (Invokerequired) {
// Dispatch to correct thread, use BeginInvoke if you don't need
// caller thread until operation completes
Invoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
请注意,当前线程将一直阻塞,直到UI线程完成方法执行为止。如果线程的时间安排很重要,那么这可能是个问题(不要忘记UI线程可能很忙或挂了一段时间)。如果不需要方法的返回值,则可以简单地替换Invoke
为BeginInvoke
,对于WinForms甚至不需要后续调用EndInvoke
:
void DoStuff() {
if (Invokerequired) {
BeginInvoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
如果需要返回值,则必须处理通常的IAsyncResult
接口。
GUI Windows应用程序基于带有消息循环的窗口过程。如果您使用纯C语言编写应用程序,则将具有以下内容:
MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
使用这几行代码,您的应用程序等待消息,然后将消息传递给窗口过程。窗口过程是一个很大的switch / case语句,在其中检查WM_
您知道的消息()并以某种方式处理它们(为绘制窗口,为之WM_PAINT
退出应用程序WM_QUIT
,依此类推)。
现在假设您有一个工作线程,如何 调用 主线程?最简单的方法是使用此基础结构完成操作。我简化了任务,但是这些步骤是:
WPF和WinForms都使用此方法将消息从线程传递(调度)到UI线程。请参阅MSDN上的这篇文章,以获取有关多个线程和用户界面的更多详细信息,WinForms隐藏了许多这些详细信息,您不必理会它们,但是您可以看看它在幕后如何工作。