UI Thread .Invoke() causing handle leak?
Asked Answered
I

8

8

In what circumstances would updating a UI control from a non-UI thread could cause the processes' handles to continually increase, when using a delegate and .InvokeRequired?

For example:

public delegate void DelegateUIUpdate();
private void UIUpdate()
{
    if (someControl.InvokeRequired)
    {
        someControl.Invoke(new DelegateUIUpdate(UIUpdate));
        return;
    }
    // do something with someControl
}

When this is called in a loop or on timer intervals, the handles for the program consistently increase.

EDIT:

If the above is commented out and amended as such:

public delegate void DelegateUIUpdate();
private void UIUpdate()
{
    //if (someControl.InvokeRequired)
    //{
    //   someControl.Invoke(new DelegateUIUpdate(UIUpdate));
    //    return;
    //}
    CheckForIllegalCrossThreadCalls = false;
    // do something with someControl
}

...then the handles stop incrementing, however I don't want to allow cross thread calls, of course.

EDIT 2:

Here is a sample that shows the handles increase:

Thread thread;
private delegate void UpdateGUI();
bool UpdateTheGui = false;

public Form1()
{
    InitializeComponent();

    thread = new Thread(new ThreadStart(MyThreadLoop));
    thread.Start();
}

private void MyThreadLoop()
{
    while (true)
    {
        Thread.Sleep(500);
        if (UpdateTheGui)
        {
            UpdateTheGui = false;
            UpdateTheGuiNow();
        }
    }
}

private void UpdateTheGuiNow()
{
    if (label1.InvokeRequired)
    {
        label1.Invoke(new UpdateGUI(UpdateTheGuiNow));
        return;
    }

    label1.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss");
    label2.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss");
    label3.Text = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss");
}

private void btnInvoke_Click(object sender, EventArgs e)
{
    UpdateTheGui = true;
}
Intercede answered 15/6, 2010 at 19:40 Comment(1)
I have the same problem here, with exactly the same call on a timer. Thanks for mentioning CheckForIllegalCrossThreadCalls because I'd never heard of it before.Gnarl
J
3

The Control.Invoke() method doesn't consume any handles. However, this code is clearly called from a thread. A Thread does consume handles, 5 of them.

The Thread class doesn't have a Dispose() method, although it ought to have one. That was probably by design, it would be very difficult to call reliably, impossibly so for threadpool threads. The 5 handles that a thread requires are released by the finalizer. Your program will require ever increasing amounts of handles if the finalizer never runs.

Not getting the finalizer to run is quite unusual. You would have to have a program that starts a lot of threads but doesn't allocate a lot of memory. This tends to only happen in static tests. You can diagnose this condition with Perfmon.exe, use the .NET memory performance counters and check if gen #0 collections are being done.

If this happens in a production program then you'll have to call GC.Collect() yourself to avoid a runaway handle leak.

Jayjaycee answered 15/6, 2010 at 20:10 Comment(4)
I have used the described pattern to update UI components in the past, without the handle-increase problem, but in the current project it is very certainly something to do with this thread-to-UI call. If I comment out the portion that checks and calls the delegate, and instead CheckForIllegalCrossThreadCalls = false; then the handles stop increasing, though I don't want to leave it in such a state. I will update the question with this info.Intercede
Well, that defies an easy explanation. Give us some idea what the rest of the code does and at what specific statement you see the handle count increase.Jayjaycee
Added a sample that shows the problem in more detail. A button on the form triggers the update.Intercede
There's some other useful discussion of this problem over here: #1603623Optative
E
4

I had the same problem with

this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e);

creating one handle each call.

The handle increments because Invoke is Synchronous and effectively the handle has been left hanging.

Either a Wait Handle should be used to process the result or the Asynchronous BeginInvoke method as shown below.

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);    
Erikaerikson answered 22/10, 2011 at 11:33 Comment(1)
Thanks guy! Your answer is sooo cool, that saved my app from OutOfMemory and 3 days of investigations. Probably you know somw profiling ninjustsu.... You are a real rascal!Set
J
3

The Control.Invoke() method doesn't consume any handles. However, this code is clearly called from a thread. A Thread does consume handles, 5 of them.

The Thread class doesn't have a Dispose() method, although it ought to have one. That was probably by design, it would be very difficult to call reliably, impossibly so for threadpool threads. The 5 handles that a thread requires are released by the finalizer. Your program will require ever increasing amounts of handles if the finalizer never runs.

Not getting the finalizer to run is quite unusual. You would have to have a program that starts a lot of threads but doesn't allocate a lot of memory. This tends to only happen in static tests. You can diagnose this condition with Perfmon.exe, use the .NET memory performance counters and check if gen #0 collections are being done.

If this happens in a production program then you'll have to call GC.Collect() yourself to avoid a runaway handle leak.

Jayjaycee answered 15/6, 2010 at 20:10 Comment(4)
I have used the described pattern to update UI components in the past, without the handle-increase problem, but in the current project it is very certainly something to do with this thread-to-UI call. If I comment out the portion that checks and calls the delegate, and instead CheckForIllegalCrossThreadCalls = false; then the handles stop increasing, though I don't want to leave it in such a state. I will update the question with this info.Intercede
Well, that defies an easy explanation. Give us some idea what the rest of the code does and at what specific statement you see the handle count increase.Jayjaycee
Added a sample that shows the problem in more detail. A button on the form triggers the update.Intercede
There's some other useful discussion of this problem over here: #1603623Optative
P
3

I've seen the same thing in my code. I fixed it by replacing Invoke with BeginInvoke. The handle leak went away.

Doron.

Porterporterage answered 3/8, 2011 at 12:29 Comment(0)
I
1

I actually see the same problem occuring as JYelton. I have the same call from within a thread to update the UI.

As soon as the line someControl.Invoke(new DelegateUIUpdate(UIUpdate)); is called, the handle increases by one. There is certainly a leak of some kind on the invoke, but I have no idea what is causing it. This has been verified on several systems.

Imagism answered 15/6, 2010 at 20:16 Comment(0)
K
1

Aync call with explicit handle finalize. Exapmle:

  public static class ActionExtensions
  {
    private static readonly ILog log = LogManager.GetLogger(typeof(ActionExtensions));

    /// <summary>
    /// Async exec action.
    /// </summary>
    /// <param name="action">Action.</param>
    public static void AsyncInvokeHandlers(
      this Action action)
    {
      if (action == null)
      {
        return;
      }

      foreach (Action handler in action.GetInvocationList())
      {
        // Initiate the asychronous call.  Include an AsyncCallback
        // delegate representing the callback method, and the data
        // needed to call EndInvoke.
        handler.BeginInvoke(
          ar =>
          {
            try
            {
              // Retrieve the delegate.
              var handlerToFinalize = (Action)ar.AsyncState;
              // Call EndInvoke to free resources.
              handlerToFinalize.EndInvoke(ar);

              var handle = ar.AsyncWaitHandle;
              if (handle.SafeWaitHandle != null && !handle.SafeWaitHandle.IsInvalid && !handle.SafeWaitHandle.IsClosed)
              {
                ((IDisposable)handle).Dispose();
              }
            }
            catch (Exception exception)
            {
              log.Error("Async Action exec error.", exception);
            }
          },
          handler);
      }
    }
  }

See http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx note:

When you use the BeginInvoke method of a delegate to call a method asynchronously and obtain a wait handle from the resulting IAsyncResult, we recommend that you close the wait handle as soon as you are finished using it, by calling the WaitHandle.Close method. If you simply release all references to the wait handle, system resources are freed when garbage collection reclaims the wait handle, but garbage collection works more efficiently when disposable objects are explicitly closed or disposed. For more information, see the AsyncResult.AsyncWaitHandle property.

Karney answered 3/4, 2014 at 12:44 Comment(0)
B
1

Here's an extension method which functions similarly to the normal Invoke call, but will clean up the handle after:

namespace ExtensionMethods
{
    public static class ExtensionMethods
    {
        public static void InvokeAndClose(this Control self, MethodInvoker func)
        {
            IAsyncResult result = self.BeginInvoke(func);
            self.EndInvoke(result);
            result.AsyncWaitHandle.Close();
        }
    }
}

You can then call it very similarly to a normal invoke:

myForm.InvokeAndClose((MethodInvoker)delegate
{
    someControl.Text = "New Value";
});

It will block and wait for the delegate to execute, then close the handle before returning.

Baculiform answered 21/7, 2015 at 1:35 Comment(0)
S
0

This is the standard pattern for using Invoke to marshall updates to the UI thread.

Are you sure your problem is not being caused by other code in your application that is not included in your question?

Sacchariferous answered 15/6, 2010 at 19:45 Comment(1)
Not entirely sure, but see the edit to the question and also the comment to Hans' answer. It's a very unusual problem.Intercede
B
0

I don't think it is related. Perhaps just waiting for the garbage collector to dispose the newly allocated object(s) inside Invoke().

Backbencher answered 15/6, 2010 at 19:45 Comment(1)
Calling GC.Collect() actually will stop the handles from increasing. Interestingly, if GC.Collect() is added after the program has been running a while, during a break point, it will not reduce the handles to an initial state, but rather simply prevent them from accumulating.Intercede

© 2022 - 2024 — McMap. All rights reserved.