2

我的表单上有一个带有以下Tick事件处理程序的 Timer 控件:

private void timer1_Tick(object sender, EventArgs e) {
    foreach (char c in "Andrew") {
        SendKeys.SendWait(c.ToString());
        System.Threading.Thread.Sleep(1000);
    }
}

由于 System.Windows.Forms.Timer 在 UI 线程上运行,我希望事件处理程序Tick在它运行时阻止进一步发生的事件,这将AndrewAndrewAndrew...作为输出提供。相反,我得到AnAnAnAn....

Tick为什么在第一个事件完成之前引发和处理后续事件?

如何确保 Timer 一次引发一个事件,并在处理程序运行完成之前完全阻塞?

我意识到 Thread.Sleep 是一种可怕的计时代码方式。我只是想知道发生了什么事。

4

2 回答 2

1

您是通过消息循环重新进入的受害者。您正在timer1_Tick通过消息循环间接递归到您的函数中。正在发生的事情是,在SendKeys.SendWait 另一个消息循环内部(而不是在不同的线程上)正在启动以监视消息是否已被处理。然后在另一个线程上,当在这个内部循环中处理消息时,计时器正在触发并发布一条消息以再次调用您的 tick 函数。欢闹随之而来。

也许一个简化的例子会有所帮助。运行它并观察输出。

public class Program
{
    private static Queue<Action> queue = new Queue<Action>();

    public static void Main(string[] args)
    {
        // put three things in the queue. 
        // In a simple world, they would finish in order.
        queue.Enqueue(() => DoWork(1));
        queue.Enqueue(() => DoComplicatedWork(2));
        queue.Enqueue(() => DoWork(3));

        PumpMessages();            
    }

    private static void PumpMessages()
    {
        while (queue.Count > 0)
        {
            Action action = queue.Dequeue();
            action();
        }
    }

    private static void DoWork(int i)
    {
        Console.WriteLine("Doing work: {0}", i);
    }

    private static void DoComplicatedWork(int i)
    {
        Console.WriteLine("Before doing complicated work: {0}", i);
        PumpMessages();
        Console.WriteLine("After doing complicated work: {0}", i);
    }

}`

您有点假设,因为在 UI 中只有一个线程泵送消息,排队的每个项目都按顺序处理。但是,当放入队列的方法本身可以泵送消息时,情况就不是这样了。在示例中,第 3 次操作实际上在第 2 次之前完成。该DoComplicatedWork方法类似于SendWait.

我应该回答你关于如何防止这种情况的第二个问题。Alock无济于事,因为它们是可重入的(即同一个线程可以多次获取锁)。最好的方法是在方法内部禁用计时器或分离滴答处理程序,并在返回之前再次重新启用/附加处理程序。您也可以尝试一个简单的boolean标志来指示您是否已经在该方法中,如果是则返回。

于 2013-01-12T08:56:15.377 回答
1

您可以使用参考源或像 Reflector 或 ILSpy 这样的良好反编译器来找出框架代码内部发生的情况。SendKeys.SendWait() 方法最终调用内部类的方法调用 SKWindow 来实现“等待”请求。该方法如下所示:

public static void Flush()
{
    Application.DoEvents();
    while ((events != null) && (events.Count > 0))
    {
        Application.DoEvents();
    }
}

DoEvents() 是一个相当臭名昭著的方法,以导致大量代码崩溃而闻名。您在计时器的 Tick 事件处理程序上获得的重新进入是相当无害的,此代码可能会对您的程序造成更大的损害。你会发现这个答案中解释了更常见的肮脏。显然,如果可以的话,您会希望避免使用 SendWait()。

于 2013-01-12T11:15:08.540 回答