【问题标题】: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 的线程,但我似乎无法找到/弄清楚如何实际实现它。

【问题讨论】:

    标签: c# winforms timer 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() 只是启动计时器,但在计时器在后台运行时立即返回。所以在将标签文本设置为firstsecond 之间几乎没有停顿。您要做的是等待计时器滴答作响,然后再次更新标签:

    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 的实例成员。那么是否存在从TimerForm 的循环引用?

    我不知道答案,我觉得这有点难以推理。因为我不知道,所以我会删除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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-13
      • 2015-02-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多