Search code examples
c#.netwinformstimerblocking

Why is SendKeys.SendWait and Thread.Sleep inside Timer_Tick event not blocking?


I have a single Timer control on my form with the following Tick event handler:

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

Since System.Windows.Forms.Timer runs on the UI thread, I would expect the event handler to block further Tick events occuring while it's running which would give AndrewAndrewAndrew... as output. Instead, I get AnAnAnAn....

Why are subsequent Tick events raised and handled before the first has finished?

How can I make sure the Timer raises one event at a time, and gets fully blocked until the handler has run to completion?

I realise Thread.Sleep is a horrible way of timing code. I just want to know what is going on.


Solution

  • You are victim of re-entry via the message loop. You are recursing into your timer1_Tick function indirectly via the message loop. What is happening is that inside SendKeys.SendWait another message loop is being spun up (not on a different thread) to monitor the whether the messages have been processed. Then on another thread, while messages are being processed in this inner loop, the timer is firing and posting a message to call to your tick function again. Hilarity ensues.

    Perhaps a simplified example will help. Run this and observe the output.

    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);
        }
    
    }`
    

    You are sort of assuming because there is only one thread pumping messages in the UI that each item that is queued is processed in order. However, this is not true when methods put into the queue can themselves pump messages. In the example, the 3rd operation actually completes before the 2nd one. The DoComplicatedWork method is analogous to what is happening in SendWait.

    I should answer your second question on how to prevent this. A lock won't help because they are re-entrant (i.e. the same thread can acquire a lock more than once). The best way is to disable the timer or detach the tick handler while inside the method and re-enable/attach the handler again before returning. You could also just try a simple boolean flag to indicate whether you are in the method already and return if so.