玩计时器。 上下文:带有两个标签的 winforms。
我想看看 System.Timers.Timer
是如何工作的,所以我没有使用 Forms 计时器。
我知道表单和 myTimer 现在将在不同的线程中运行。
有没有一种简单的方法可以用以下形式在 lblValue
上表示耗时?
我在 MSDN 上看过这里但是有没有更简单的方法!
这是 winforms 代码:
using System.Timers;
namespace Ariport_Parking
{
public partial class AirportParking : Form
{
//instance variables of the form
System.Timers.Timer myTimer;
int ElapsedCounter = 0;
int MaxTime = 5000;
int elapsedTime = 0;
static int tickLength = 100;
public AirportParking()
{
InitializeComponent();
keepingTime();
lblValue.Text = "hello";
}
//method for keeping time
public void keepingTime() {
myTimer = new System.Timers.Timer(tickLength);
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){
myTimer.Stop();
ElapsedCounter += 1;
elapsedTime += tickLength;
if (elapsedTime < MaxTime)
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
myTimer.Start();
}
else
{ myTimer.Start(); }
}
}
}
最佳答案
我猜你的代码只是一个测试,所以我不会讨论你用你的计时器做了什么。这里的问题是如何在计时器回调中使用用户界面控件执行某些操作。
大多数 Control
的方法和属性只能从 UI 线程访问(实际上它们只能从您创建它们的线程访问,但这是另一回事)。这是因为每个线程都必须有自己的消息循环(GetMessage()
按线程过滤消息),然后要使用 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
接口(interface)。
它是如何工作的?
GUI Windows 应用程序基于具有消息循环的窗口过程。如果你用纯 C 编写应用程序,你会得到类似这样的东西:
MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
通过这几行代码,您的应用程序等待一条消息,然后将消息传递给窗口过程。窗口过程是一个很大的 switch/case 语句,你可以在其中检查你知道的消息 (WM_
) 并以某种方式处理它们(你为 WM_PAINT
绘制窗口,你退出你的WM_QUIT
等申请)。
现在假设您有一个工作线程,如何调用您的主线程?最简单的方法是使用这个底层结构来解决问题。我过度简化了任务,但这些是步骤:
- 创建一个(线程安全的)函数队列来调用(一些示例 here on SO)。
- 向窗口过程发送自定义消息。如果您将此队列设置为优先级队列,那么您甚至可以决定这些调用的优先级(例如,来自工作线程的进度通知的优先级可能低于警报通知)。
- 在窗口过程中(在您的 switch/case 语句中)您理解该消息,然后您可以查看要从队列中调用的函数并调用它。
WPF 和 WinForms 都使用此方法将消息从线程传递(调度)到 UI 线程。看看this article on MSDN有关多线程和用户界面的更多详细信息,WinForms 隐藏了很多这些详细信息,您不必关心它们,但您可以看一下以了解它在后台的工作原理。
关于c# - 如何从另一个线程调用 UI 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10170448/