C# 这个阻塞线程调用是如何实现的;“释放”;它本身

C# 这个阻塞线程调用是如何实现的;“释放”;它本身,c#,multithreading,winforms,timer,C#,Multithreading,Winforms,Timer,我正在运行一个基本的单线程应用程序 通常,在调用System.Windows.Forms.MessageBox.Show()时,人们希望此调用能够有效地阻止进一步的执行,直到此方法返回为止 但是,当使用System.Windows.Forms.Timer时,*相同的*线程似乎在某种程度上释放了自己,计时器的勾号事件在同一线程上触发 到底发生了什么事?我觉得这可能与穿线公寓有关,但我想澄清一下 以最简单的形式重新创建为控制台应用程序,如下所示: class Program { static

我正在运行一个基本的单线程应用程序

通常,在调用
System.Windows.Forms.MessageBox.Show()
时,人们希望此调用能够有效地阻止进一步的执行,直到此方法返回为止

但是,当使用
System.Windows.Forms.Timer
时,*相同的*线程似乎在某种程度上释放了自己,
计时器的
勾号事件在同一线程上触发

到底发生了什么事?我觉得这可能与穿线公寓有关,但我想澄清一下

以最简单的形式重新创建为控制台应用程序,如下所示:

class Program
{
    static void Main(string[] args)
    {
        new Program();
        while (true)
        {
            System.Windows.Forms.Application.DoEvents();
        }
    }

    private System.Windows.Forms.Timer timer;

    public Program()
    {
        timer = new System.Windows.Forms.Timer() { Interval = 2000 };
        timer.Tick += timer_Tick;
        timer.Start();
    }

   private void timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine(string.Format("Thread {0} has entered", Thread.CurrentThread.ManagedThreadId)); 
        var result = MessageBox.Show("Test");
        Console.WriteLine(string.Format("Thread {0} has left", Thread.CurrentThread.ManagedThreadId)); 
    }
}
输出:

线程10已进入
线程10已进入
线程10已进入
线程10已进入
线程10已进入


当显示消息框等模式窗口时,Windows消息泵将继续运行

如果没有,当您在模态窗口前面移动模态窗口时,模态窗口后面的窗口显示将不会更新

由于Windows消息仍在泵送,“WM_定时器”消息仍将发送到非前台窗口,因此您将看到您注意到的行为

需要注意的关键是,Windows计时器会导致Windows将“WM_timer”消息发布到窗口的事件队列中,只要窗口的消息泵正在运行,计时器事件就会继续处理

避免再次进入的一个常见方法是在处理滴答声时禁用计时器

例如,将记号处理代码放入名为
handleTimer()
的方法中,然后按如下方式处理记号:

private void timer_Tick(object sender, EventArgs e)
{
    timer.Enabled = false;

    try
    {
        handleTimer();
    }

    finally 
    {
        timer.Enabled = true;
    }
}

(发生异常时,您可能不希望重新启用计时器,在这种情况下,您不需要上面的
try/finally
逻辑。)

当显示消息框等模式窗口时,Windows消息泵将继续运行

如果没有,当您在模态窗口前面移动模态窗口时,模态窗口后面的窗口显示将不会更新

由于Windows消息仍在泵送,“WM_定时器”消息仍将发送到非前台窗口,因此您将看到您注意到的行为

需要注意的关键是,Windows计时器会导致Windows将“WM_timer”消息发布到窗口的事件队列中,只要窗口的消息泵正在运行,计时器事件就会继续处理

避免再次进入的一个常见方法是在处理滴答声时禁用计时器

例如,将记号处理代码放入名为
handleTimer()
的方法中,然后按如下方式处理记号:

private void timer_Tick(object sender, EventArgs e)
{
    timer.Enabled = false;

    try
    {
        handleTimer();
    }

    finally 
    {
        timer.Enabled = true;
    }
}

(在发生异常时,您可能不想重新启用计时器,在这种情况下,您不需要上面的
try/finally
逻辑。)

这不是一个好的测试程序,因为调用
Application.DoEvents()
将准确给出这种行为。你能用一个简单的Windows Forms应用程序复制它吗?我只是在我的简短示例中调用它,因为这是默认的winforms应用程序所能做的。默认的winforms应用程序不会调用
Application.DoEvents()
创建一个winforms应用程序,在其上粘贴一个winforms计时器,你会观察到完全相同的行为。事实上,但我刚才指出,控制台应用程序调用Application.DoEvents()并不是一个等价的测试。这不是一个好的测试程序,因为调用
Application.DoEvents()
将给出这种行为。你能用一个简单的Windows Forms应用程序复制它吗?我只是在我的简短示例中调用它,因为这是默认的winforms应用程序所能做的。默认的winforms应用程序不会调用
Application.DoEvents()
创建一个winforms应用程序,在其上粘贴一个winforms计时器,你会观察到完全相同的行为。事实上,但我刚刚指出,控制台应用程序调用Application.DoEvents()并不是一个等价的测试。没有“同一线程”锁这样的东西,那么除了手动使用
bool
手动阻止之外,还有什么方法可以阻止这种重复吗?@maxp我通常做的是调用
timer.Enabled=false
作为处理程序中的第一件事,然后
timer.Enabled=true作为最后一件事。我将把它添加到我的答案中。没有“同一线程”锁这样的东西,那么除了手动使用
bool
来手动阻止之外,还有什么方法可以阻止这种重复吗?@maxp我通常做的是调用
timer.Enabled=false
作为处理程序中的第一件事,然后
timer.Enabled=true作为最后一件事。我会在我的回答中加上这个。