3

I have a question about System.Windows.Forms.Timer. Is it possible to get Tick event after disposing it? For example, if the message is in the message loop and I dispose the timer meanwhile. If it is possible what is the best way to prevent against it. Do you now any good sources explaining it, because I couldn't find anything explaining it. Here is same code explaining my problem:

namespace TestTimer
{
    public partial class Form1 : Form
    {
        ObjectWithTimer obj = null;
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if(obj != null) 
            {
                obj.Deinit();
                obj = null;
            }
            obj = new ObjectWithTimer();
        }
    }

    public class ObjectWithTimer
    {
        public Object o = new object();
        public Timer timer = new Timer();
        bool disposed = false;

        public ObjectWithTimer()
        {
            timer.Interval = 10;
            timer.Tick += new EventHandler(timer_Tick);
            timer.Enabled = true;
        }

        public void Deinit()
        {
            timer.Enabled = false;
            timer.Tick -= new EventHandler(timer_Tick);
            timer.Dispose();
            timer = null;
            disposed = true;
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            if (disposed)
            {
                //Is it possible to get here
                if (timer.Enabled) return;
            }
            //doing something 
        }
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Marcin
  • 33
  • 5
  • 1
    Yes, that can happen. Especially since you don't unsubscribe, but subscribe again in `Deinit()` (`timer.Tick += ...` ). – CodeCaster Sep 08 '15 at 08:45
  • Why a `Deinit` method instead of the expected `IDisposable.Dispose`? `Deinit` prevents the use of a safe `using` clause, requiring the calling code to handle exceptions etc just as `using` would. As it is, this class will almost certainly leak timers – Panagiotis Kanavos Sep 08 '15 at 09:01
  • @PanagiotisKanavos That's another story. I wrote that example just to picture my doubts about Timer. – Marcin Sep 08 '15 at 12:59
  • @CodeCaster Of course in `Deinitialize()` it supposed to be `timer.Tick -= new EventHandler(timer_Tick);` – Marcin Sep 09 '15 at 10:50
  • Yeah of course it is _supposed_ to be that, but you didn't post that. I can't see whether this is the actual code you run. – CodeCaster Sep 09 '15 at 10:57
  • @CodeCaster That's not code from any application. I wrote it just to explain my problem – Marcin Sep 09 '15 at 13:03
  • Yeah then make sure it's OK before posting, as it only adds to the confusion. – CodeCaster Sep 09 '15 at 13:04

3 Answers3

4

Understanding how timers work can help you feel better about it. They are implemented by the operating system, the underlying winapi call to start a timer is SetTimer(). The OS then posts a notification whenever the timer ticks, you get a WM_TIMER message. The plumbing in Winforms ensures that your Tick event handler runs when this message is received.

These messages are stored in the message queue, an internal data structure associated with a window. This queue serializes messages, it is the basic mechanism that ensures that you for example can never lose a mouse click or a keyboard key press, even when the window is unresponsive because the UI thread is busy with something else.

This queue gives reason to be cautious, what happens when the queue stores a WM_TIMER message when you disposed the timer? Unless something drastic is done, you'd still get that message and your Tick event handler will fire.

But no need to worry, WM_TIMER belongs to a small group of messages that are generated in a special way. They are synthesized messages, it is only ever generated when your program asks for a message with GetMessage(). Other common messages that belong that group are WM_PAINT, it fires the Paint event. Note how you can call Invalidate() as often as you like, you still get only a single Paint event. WM_MOUSEMOVE is another one, it fires the MouseMove event. Something you can reason about, no matter how fast you move the mouse, you can never flood the message queue with mouse-move messages.

Another characteristic of these synthesized messages is that they appear to have a "low priority". Given is that they are only ever synthesized when the message queue is empty. Which is why keyboard messages and mouse clicks always generate an event ahead of a paint.

Long story short, you can only get a WM_TIMER message if you ask for a message and the timer is still alive. The Timer.Dispose() method calls KillTimer() under the hood. Which ends any opportunity to still get a Tick event. Only possible way that could get screwed up is when you call the Stop() or Dispose() methods from a worker thread. Don't do that.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

The Windows Forms Timer is single threaded so is not possible that while disposing it you are in timer_Tick.

Also you are not detaching your event in deinit function.

Giangregorio
  • 1,482
  • 2
  • 11
  • 12
  • 3
    [Timer events generally fire on a different thread](http://stackoverflow.com/questions/1435876/do-c-sharp-timers-elapse-on-a-separate-thread). – CodeCaster Sep 08 '15 at 08:48
  • They are talkin about different timer, one is the windows forms one, and one is the System.Timers.Timer. – Giangregorio Sep 08 '15 at 08:50
  • 3
    That's not the issue - the problem is that the tick could have already been posted to the synchronization context while you're doing something else on the UI thread (which isn't too hard, given that WM_TIMER is the lowest priority message). The question is, if this happens, does the `Tick` event fire or not? – Luaan Sep 08 '15 at 08:52
  • @CodeCaster they are different timer, it's not the same – Giangregorio Sep 08 '15 at 09:06
  • @Giangregorio Of course in `Deinitialize()` it supposed to be `timer.Tick -= new EventHandler(timer_Tick);` – Marcin Sep 08 '15 at 12:56
1

This is very easy to test. I've modified your code a bit:

public class Form1 : Form
{
    public Form1()
    {
        var button = new Button();
        button.Click += button1_Click;

        Controls.Add(button);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var obj = new ObjectWithTimer();

        Thread.Sleep(2000);

        obj.Deinit();
    }
}

public class ObjectWithTimer
{
    public System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
    bool disposed = false;

    public ObjectWithTimer()
    {
      timer.Interval = 100;
      timer.Tick += new EventHandler(timer_Tick);
      timer.Enabled = true;
    }

    public void Deinit()
    {
      timer.Enabled = false;
      timer.Tick -= new EventHandler(timer_Tick);
      timer.Dispose();
      timer = null;
      disposed = true;
    }

    private void timer_Tick(object sender, EventArgs e)
    {
      "Ticked".Dump();
    }
}

The Thread.Sleep ensures the UI thread is occupied while the timer does its ticking.

The result? No, the Tick will not fire after the timer is disabled. Even the timer.Tick -= new EventHandler(timer_Tick); is unnecessary.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • As far as I understand it `System.Window.Forms.Timer` works on main thread. Calling `Sleep()` on main thread probably will prevent `Timer` from ticking. – Marcin Sep 08 '15 at 13:32
  • @Marcin No, it will only prevent it from handling the tick immediately. It's basically a coöperative timer. In any case, that's exactly the reason why I did this - to show that you will *not* get ticks if you disable the timer; there's no race condition. – Luaan Sep 08 '15 at 14:32
  • After reading @HansPassant post and after I did same research I'm pretty sure that `WM_TIMER` message won't be generated and the timer won't tick. To generate `WM_TIMER` message you need your message loop keep running, but calling `Sleep()` on main thread freezes massage loop. – Marcin Sep 16 '15 at 10:13
  • @Marcin Yes, Hans explains this a lot better than I do. `WM_TIMER` is not a "real" message in the queue, so it behaves a bit differently than e.g. `WM_CLICK`. The end result is still the same, though - `Tick` will not fire after the timer is disabled. – Luaan Sep 16 '15 at 15:04
  • @Marcin With a bit of hacking, though, it *is* possible to cause the undesirable behaviour - http://blogs.msdn.com/b/oldnewthing/archive/2014/12/05/10578385.aspx is a nice illustration. However, if you're only using managed C# (and thus, no `PeekMessage`), it's not going to happen. The closest you can get in safe C# is `Application.DoEvents`, which would allow the timer message to be posted - however, the callback would still be executed before you dispose the timer, so it doesn't violate any of the contracts. – Luaan Sep 18 '15 at 08:12
  • I'm quite safe without using such tricks, but thank you for finding this interesting case. – Marcin Sep 19 '15 at 10:00