C# 如何使用计时器等待?

C# 如何使用计时器等待?,c#,winforms,timer,sleep,C#,Winforms,Timer,Sleep,我试图通过使用计时器来延迟方法中的事件,但是我不一定理解如何使用计时器来等待 我将计时器设置为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 w

我试图通过使用计时器来延迟方法中的事件,但是我不一定理解如何使用计时器来等待

我将计时器设置为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的文章,但我似乎无法找到/弄清楚如何真正实现它。

如果你想做的只是在计时器滴答作响时更改文本,你会不会更好地推迟

label1.Text = "second";
…在将计时器更改为enabled=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();
}
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()
启动计时器

如注释中所述,您可以将计时器创建放入一个方法中,如下所示(注意:这是未测试的):

然后你可以这样使用它:

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    Delayed(2000, () => label1.Text = "second");
}
特吉弗的后续行动 使用延迟是否包含内存泄漏(引用泄漏)

订阅事件始终会创建双向引用

在本例中,
timer.Tick
获取对匿名函数(lambda)的引用。该函数提升一个局部变量
timer
,尽管它是一个引用,而不是一个值,并且包含对传入操作委托的引用。该委托将包含对
标签1
的引用,该标签是
表单的一个实例成员。那么从
计时器到
表单是否有循环引用

我不知道答案,我觉得有点难以推理。因为我不知道,我会在
Delayed
中删除lambda的使用,使其成为一个合适的方法,并且让它除了停止计时器(该方法的
sender
参数)之外,还删除事件

通常lambda不会导致垃圾收集问题。在这种情况下,计时器实例仅在本地存在,lambda中的引用不会阻止垃圾收集来收集实例(另请参阅)

实际上,我使用.NET内存分析器再次测试了这一点。计时器对象收集得很好,没有发生泄漏。探查器确实给了我一个警告,有一些实例“[…]被垃圾收集而没有被正确处理”。但是,移除事件处理程序本身(通过保留对它的引用)并不能解决这一问题。将捕获的计时器引用更改为
(timer)s
,也不会改变这一点

显然,停止计时器后在事件处理程序中调用
timer.Dispose()
,这有帮助,但我认为这是否确实必要。我认为探查器警告/注释没有那么重要。

如果您使用的是C#5.0
wait
,那么这就容易多了:


使用
Sysmtes.Timers.Timer
此代码不起作用,您需要封送到UI线程。
窗体
计时器的优点是您不需要这样做。这是真的,忘了它吧。我要么删除该部分,要么在其中添加一些invokey内容,只是不想混淆OP。因此,如果我有很多地方需要等待,那么每次等待都会有一个唯一的计时器,对吗?是的,您可以将样板计时器创建抽象为一个函数,然后像
delay(2000,()=>label1.Text=“second”)那样调用它;。我遇到的问题是,否则该方法将调用大约6个事件,每个事件都需要等待,这会导致问题,因为timer.start()在继续之前不会等待计时器执行,它只是启动计时器,然后继续下一行。啊,答案是,当您启动System.Windows.Forms.timer时,它创建(并保存)System.Windows.Forms.NativeWindow对象,该对象将自身添加到将本机窗口句柄与NativeWindow对象关联的查找表中。当计时器被销毁时,该对象将从地图中删除。因此,有一个引用在它工作时保持它的活动状态。为了准确起见,Timer创建了一个Timer.TimerNativeWindow的子类Timer.TimerNativeWindow对象,它有一个指向Timer对象的后指针,因此是保持活动状态的引用。好主意;我认为这是最干净的方法。然而,我认为“async”关键字必须在“void”关键字之前。这个想法太糟糕了。WinForms事件中的延迟会导致奇怪的行为(通过暂停消息泵)。WinForms是单身-threaded@smirkingman您只需自己运行代码就可以看到,因为这是异步的,所以消息泵没有被阻塞。Winforms也不是“单线程”。您应该只从一个线程与UI进行交互,但您完全可以使用其他线程进行非UI工作,而不是因为此特定问题需要(或使用)任何其他线程来解决此问题而不阻塞UI。
private void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    Delayed(2000, () => label1.Text = "second");
}
private async void button1_Click(object sender, EventArgs e)
{
    label1.Text = "first";
    await Task.Delay(2000);
    label1.Text = "second";
}
       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);