如何使用计时器等待?
Posted
技术标签:
【中文标题】如何使用计时器等待?【英文标题】:How to use a timer to wait? 【发布时间】:2012-12-17 01:19:46 【问题描述】:我试图通过使用计时器来延迟我的方法中的事件,但是我不一定了解如何使用计时器来等待。
我将计时器设置为 2 秒,但是当我运行此代码时,最后一次调用运行时没有 2 秒延迟。
Timer timer = new Timer();
timer.Tick += new EventHandler(timer_Tick); // Everytime timer ticks, timer_Tick will be called
timer.Interval = (1000) * (2); // Timer will tick evert second
timer.Enabled = true; // Enable the timer
void timer_Tick(object sender, EventArgs e)
timer.Stop();
private void button1_Click(object sender, EventArgs e)
label1.Text = "first";
timer.Start();
label1.Text = "second";
所以当我点击我的按钮时,它会立即将 label1 显示为“第二”,而不是更改为“第一”,等待 2 秒,然后更改为“第二”。我在这里阅读了很多关于使用计时器而不是 thread.sleep 的线程,但我似乎无法找到/弄清楚如何实际实现它。
【问题讨论】:
【参考方案1】:如果您只想在计时器滴答作响时更改文本,那么您最好不要放...
label1.Text = "second";
...在计时器滴答声中,在您将计时器更改为启用之前或之后 = false;
像这样;
void timer_Tick(object sender, EventArgs e)
timer.Stop();
label1.Text = "second";
private void button1_Click(object sender, EventArgs e)
label1.Text = "first";
timer.Start();
【讨论】:
使用Sysmtes.Timers.Timer
这个代码不起作用,你需要编组到 UI 线程。 Forms
计时器的优点是您不需要这样做。
说的很对,忘记了。我要么删除那部分,要么在其中添加一些调用的东西,只是不想混淆 OP。【参考方案2】:
timer.Start()
只是启动计时器,但在计时器在后台运行时立即返回。所以在将标签文本设置为first
和second
之间几乎没有停顿。您要做的是等待计时器滴答作响,然后再次更新标签:
void timer_Tick(object sender, EventArgs e)
timer.Stop();
label1.Text = "second";
private void button1_Click(object sender, EventArgs e)
label1.Text = "first";
timer.Start();
顺便说一句。您不应该将timer.Enabled
设置为true,您已经使用timer.Start()
启动计时器。
如 cmets 中所述,您可以将计时器创建放入一个方法中,如下所示(注意:这是未经测试的):
public void Delayed(int delay, Action action)
Timer timer = new Timer();
timer.Interval = delay;
timer.Tick += (s, e) =>
action();
timer.Stop();
;
timer.Start();
然后你可以像这样使用它:
private void button1_Click(object sender, EventArgs e)
label1.Text = "first";
Delayed(2000, () => label1.Text = "second");
Tergiver 的跟进
使用 Delayed 是否包含内存泄漏(引用泄漏)?
订阅一个事件总是会创建一个双向引用。
在这种情况下,
timer.Tick
获取对匿名函数 (lambda) 的引用。该函数提升了一个局部变量timer
,尽管它是一个引用,而不是一个值,并且包含对传入的 Action 委托的引用。该委托将包含对label1
的引用,Form
的实例成员。那么是否存在从Timer
到Form
的循环引用?我不知道答案,我觉得这有点难以推理。因为我不知道,所以我会删除
Delayed
中对lambda 的使用,使其成为正确的方法并拥有它,除了停止计时器(这是方法的sender
参数)之外,还要删除事件。
通常 lambda 不会导致垃圾收集问题。在这种情况下,计时器实例仅存在于本地,并且 lambda 中的引用不会阻止垃圾收集器收集实例(另请参阅this question)。
我实际上使用 .NET Memory Profiler 再次对此进行了测试。计时器对象收集得很好,没有发生泄漏。探查器确实给了我一个警告,尽管有些情况“[...] 已经被垃圾收集而没有被正确处理”。删除事件处理程序本身(通过保留对它的引用)并没有解决这个问题。将捕获的计时器引用更改为 (Timer)s
也没有改变。
有什么帮助——显然——是在停止计时器后在事件处理程序中调用timer.Dispose()
,但我认为这是否真的有必要。我不认为探查器警告/注释那么重要。
【讨论】:
所以如果我有很多地方需要等待,我最终会为每个等待设置一个唯一的计时器,对吗? 是的,您可以将样板计时器创建抽象为一个函数,然后像delay(2000, () => label1.Text = "second");
一样调用它。
我遇到的问题是,否则该方法会调用大约 6 个事件,并且每个事件都需要等待,这会导致问题,因为 timer.start() 不会等待计时器在继续之前执行,它只是启动计时器然后继续到下一行。
啊,答案是,当您启动 System.Windows.Forms.Timer 时,它会创建(并保存)一个 System.Windows.Forms.NativeWindow 对象,该对象将自身添加到关联的查找表中带有 NativeWindow 对象的本机窗口句柄。当计时器被销毁时,该对象将从地图中移除。所以有一个参考让它在它工作的时候保持活力。
为了准确起见,Timer 创建了一个 Timer.TimerNativeWindow 对象,它是 NativeWindow 的一个子类,它有一个指向 Timer 对象的反向指针,因此是保活引用。【参考方案3】:
如果您使用的是 C# 5.0 await
,这会更容易:
private async void button1_Click(object sender, EventArgs e)
label1.Text = "first";
await Task.Delay(2000);
label1.Text = "second";
【讨论】:
好主意;我认为这是最干净的方法。但是,我认为“async”关键字需要在“void”关键字之前。 糟糕的想法。 WinForms 事件中的 Task.Delay 是奇怪行为的秘诀(通过停止消息泵)。 WinForms 是单线程的 @smirkingman 您可以简单地自己运行代码来查看,因为这是异步,消息泵不会被阻塞。 Winforms 也不是“单线程”的。您应该只从一个线程与 UI 进行交互,但您绝对可以使用额外的线程来处理非 UI 工作,并不是说这个特定问题需要(也不使用)任何额外的线程来解决这个问题而不阻塞 UI。【参考方案4】: private bool Delay(int millisecond)
Stopwatch sw = new Stopwatch();
sw.Start();
bool flag = false;
while (!flag)
if (sw.ElapsedMilliseconds > millisecond)
flag = true;
sw.Stop();
return true;
bool del = Delay(1000);
【讨论】:
以上是关于如何使用计时器等待?的主要内容,如果未能解决你的问题,请参考以下文章