Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timer object exists beyond window object lifetime

Tags:

c#

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.

like image 948
RB84 Avatar asked May 08 '26 21:05

RB84


1 Answers

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.

like image 152
Julien Lebosquain Avatar answered May 10 '26 11:05

Julien Lebosquain



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!