Timer object exists beyond window object lifetime
c#
Solution
First of all, the Garbage Collector isn't deterministic. Even if your window is closed and not referenced anywhere, a possibly long time can elapse before the window is actually collected. You should unsubscribe from the `Tick` event and set `IsEnabled` to `false` as soon as the window is closed.
That being said, the real problem here is the `System.Windows.Forms.Timer` itself. As soon as it's enabled, it allocates a `GCHandle` for itself, preventing its garbage collection. The event handler then prevents the window from being collected, not the other way as it's usually the case, and as you think is happening.
Note that `System.Windows.Forms.Timer` disables itself when its disposed, preventing this problem, and all components of a `Form` are automatically disposed when the form closes. But you're not registering the `Timer` as a form component, thus `Dispose` is never called automatically. You should add a `Timer` to your form through the toolbox, or instantiate it using `new Timer(components)` to see the problem disappear.
Problem
I'm showing a `Forms` window as dialogbox ``` private void buttonOverview_Click(object sender, EventArgs e) { (new OverviewBox()).ShowDialog(); MessageBox.Show("Window Exited"); } ``` `OverviewBox` has a refresh timer that is instantiated within the constructor ``` public OverviewBox() { InitializeComponent(); this._polltimer = new Timer { Interval = 30000, Enabled = true }; this._polltimer.Tick += (sender, e) => { this.Poll(); }; } ``` The method `Poll` asynchronously gets data from the database and updates the view without freezing it. ``` private void Poll() { Task.Run(() => { if (!SessionContext.Connectable()) { return; } try { [logics to get data] this.dgvChangeCoordinators.BeginInvoke(new Action(() => { SetDataGridView(this.dataGridView, "<Data Description>", listwithdata); })); } catch (Exception ex) { Logger.Log(ex.ToString()); throw; } }); } ``` `SetDataGridView` sets the list as `itemsource` of a `datagridview` and displays the data description. Sometimes however, my users complain about exceptions. The exception log looks like this: ``` 7/15/2013 5:00:10 PM: System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args) at System.Windows.Forms.Control.BeginInvoke(Delegate method) at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5() 7/15/2013 5:00:23 PM: System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args) at System.Windows.Forms.Control.BeginInvoke(Delegate method) at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5() 7/15/2013 5:00:40 PM: System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args) at System.Windows.Forms.Control.BeginInvoke(Delegate method) at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5() 7/15/2013 5:00:53 PM: System.InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args) at System.Windows.Forms.Control.BeginInvoke(Delegate method) at FormulierGenerator.Views.Agent.OverviewBox.<Poll>b__5() ``` From the time difference between the exceptions, I conclude that at least two instances of the timer are still active (30 seconds between polls, 4 different times for which groups of 2 are within 30 seconds, the poll interval). However, I cannot simulate the issue by just starting and closing the overview twice. I suspect a `GC` related issue where the window object is collected at some point in time, but the poller keeps on existing. When it tries to update the window in the window thread context, it fails. But then, shouldn't the Window object and all it's content only exist in the context of private void `buttonOverview_Click`? Added a MessageBox.Show() call to the button method to test if the method is completed after closing the dialog. It does show. Set a breakpoint on the `Poll` method to see if it was still called after the dialog was closed. It was, so the poller is definitely living longer then the window is visible. My question is, are my conclusions correct so far? If so, how can the poller continue to exist even though the context wherein the object creating the timer has been instantiated, is no longer existent e.g. how to prevent the poller from living on way past window close? Thinking of an unload event action but do not know if that is the best solution.