2

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.

Community
  • 1
  • 1
Flash
  • 15,945
  • 13
  • 70
  • 98
  • This might be me having another one of those days (I'm not big on using the Timer).. but it sounds to me like the timer event is raised on the UI thread.. but the actual timing is happening on another.. – Simon Whitehead Jan 12 '13 at 08:51

2 Answers2

1

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.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • Can you please explain this in more depth? – Flash Jan 12 '13 at 09:12
  • @Andrew, yes sample on the way. – Mike Zboray Jan 12 '13 at 09:16
  • Thanks that's very helpful. If I understand you, `SendWait` spins up its own loop which triggers the next `Tick` handler (which is in the queue because another thread constantly pushes it there when the time elapses). Why would `SendWait` start its own loop? – Flash Jan 12 '13 at 09:44
  • @Andrew I'd guess that `SendWait` needs to do that to be notified that the message it sent was processed. – Mike Zboray Jan 12 '13 at 09:57
1

You can use the Reference Source or a good decompiler like Reflector or ILSpy to find out what's going on inside the framework code. The SendKeys.SendWait() method ends up calling a method on an internal class calls SKWindow to implement the "wait" request. That method looks like this:

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

DoEvents() is rather a notorious method, famous for crashing a lot of code. The re-entrancy you got on the timer's Tick event handler is fairly innocuous, this code can do a lot more damage to your program. You'll find the more common nastiness explained in this answer. Clearly you'll want to avoid SendWait() if you can.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I haven't seen that since VB6. Do you happen to know the difference between `Send` and `SendWait`? Would `Send` be safer, since presumably it doesn't call `DoEvents`? – Flash Jan 12 '13 at 11:25
  • Right, SendKeys.Send() doesn't call the Flush() method. – Hans Passant Jan 12 '13 at 11:49