为什么是SendKeys.SendWait和Thread.在没有阻塞的Timer_Tick事件中睡觉
本文关键字:Timer Tick 事件 SendWait SendKeys Thread 为什么 | 更新日期: 2023-09-27 17:51:23
我有一个定时器控制我的表单与以下Tick
事件处理程序:
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
事件在第一个事件完成之前被引发和处理?
我如何确保定时器一次引发一个事件,并得到完全阻塞,直到处理程序已经运行到完成?
我意识到线程。睡眠是计时代码的糟糕方式。我只是想知道发生了什么事。
您是通过消息循环重新进入的受害者。您通过消息循环间接递归到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中只有一个线程抽取消息,所以排队的每个项目都是按顺序处理的。但是,当放入队列的方法本身可以泵送消息时,情况就不一样了。在这个例子中,第三个操作实际上在第二个操作之前完成。DoComplicatedWork
方法与SendWait
类似。
lock
没有帮助,因为它们是可重入的(即同一个线程可以多次获得锁)。最好的方法是在方法内部禁用计时器或分离tick处理程序,并在返回之前重新启用/附加处理程序。您也可以尝试一个简单的boolean
标志来指示您是否已经在方法中,如果是,则返回。
您可以使用参考源代码或好的反编译器(如Reflector或ILSpy)来查找框架代码内部发生的事情。SendKeys.SendWait()方法最终调用调用SKWindow的内部类上的方法来实现"等待"请求。这个方法看起来像这样:
public static void Flush()
{
Application.DoEvents();
while ((events != null) && (events.Count > 0))
{
Application.DoEvents();
}
}
DoEvents()是一个相当臭名昭著的方法,以崩溃大量代码而闻名。计时器的Tick事件处理程序的重入性是相当无害的,这段代码可以对程序造成很多大的损害。你会发现在这个答案中解释了更常见的肮脏。显然,如果可以的话,您希望避免使用SendWait()。