How to avoid leaking handles when invoking in UI from System.Threading.Timer?
Asked Answered
I

4

8

It seems like calling Invoke on a winforms control in a callback from a System.Threading.Timer leaks handles until the timer is disposed. Does anyone have an idea of how to work around this? I need to poll for a value every second and update the UI accordingly.

I tried it in a test project to make sure that that was indeed the cause of the leak, which is simply the following:

    System.Threading.Timer timer;
    public Form1()
    {
        InitializeComponent();
        timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
    }
    void DoStuff(object o)
    {
        this.Invoke(new Action(() => this.Text = "hello world"));
    }

This will leak 2 handles/second if you watch in the windows task manager.

Illfavored answered 21/10, 2009 at 19:39 Comment(2)
Rule#1 every C++ adheres to: EVERY new MUST be followed by delete!Sodamide
Yea, I was wondering how that worked, by putting a delegate out there for every callback seems like it would cause the handles to be created.Dateless
C
6

Invoke acts like a BeginInvoke/EndInvoke pair in that it posts the message to the UI thread, creates a handle, and waits on that handle to determine when the Invoked method is complete. It is this handle that is "leaking." You can see that these are unnamed events using Process Explorer to monitor the handles while the application is running.

If IASyncResult was IDisposable, disposal of the object would take care of cleaning up the handle. As it is not, the handles get cleaned up when the garbage collector runs and calls the finalizer of the IASyncResult object. You can see this by adding a GC.Collect() after every 20 calls to DoStuff -- the handle count drops every 20 seconds. Of course, "solving" the problem by adding calls to GC.Collect() is the wrong way to address the issue; let the garbage collector do its own job.

If you do not need the Invoke call to be synchronous, use BeginInvoke instead of Invoke and do not call EndInvoke; the end-result will do the same thing but no handles will be created or "leaked."

Cartan answered 21/10, 2009 at 20:40 Comment(1)
I noticed that it did go away on it's own and so I just left it at that, but thank you for the explanation.Illfavored
L
2

Is there some reason you cannot use a System.Windows.Forms.Timer here? If the timer is bound to that form you won't even need to invoke.

Lilongwe answered 21/10, 2009 at 19:48 Comment(4)
In the actual code the timer isn't in the form, it's in the presenter calling into the view. I tried changing to a System.Windows.Forms.Timer but it wasn't firing for some reason and I didn't spend too much time trying to figure out why and figured it had something to do with it not being in a class derived from ControlIllfavored
I really would recommend using Windows.Forms.Timer for this (not that it wouldn't be nice to understand the handle leak too!). Did you set the Enabled property on the Timer when you tried it? I can't see any reason why it would need to be created by a code in a class derived from Control - how would it know, for a start?Disunion
I set an Interval, gave the Tick event a handler, and called Start() on it, but break point on the event handler never got hit. Anyway, it looks like there wasn't actually a handle leak, so much as the garbage collector takes a while to get around to it. See my own answer.Illfavored
It won't run because the containing thread is non-pumping. It needs to be owned by the form.Lilongwe
I
2

Okay, I gave it a little more time and it looks like it's actually not leaking handles, it's just the indeterminate nature of the garbage collector. I bumped it way up to 10ms per tick and it'd climb up really fast and 30 seconds later would drop back down.

TO confirm the theory I manually called GC.Collect() on each callback (Don't do this in real projects, this was just to test, there's numerous articles out there about why it's a bad idea) and the handle count was stable.

Illfavored answered 21/10, 2009 at 20:44 Comment(0)
D
0

Interesting - This is not an answer but based on Andrei's comment I would have thought this would not leak handles the same way however it does leak handles at the same rate the OP mentioned.

System.Threading.Timer timer;
    public Form2()
    {
        InitializeComponent();

    }

    private void UpdateFormTextCallback()
    {
        this.Text = "Hello World!";
    }

    private Action UpdateFormText;

    private void DoStuff(object value)
    {
        this.Invoke(UpdateFormText);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
        UpdateFormText = new Action(UpdateFormTextCallback);
    }
Dateless answered 21/10, 2009 at 20:0 Comment(1)
Could this be a compartment marshaling issue without cleanup?Copulation

© 2022 - 2024 — McMap. All rights reserved.