UI Thread .Invoke()导致句柄泄漏?

时间:2010-06-15 19:40:09

标签: c# memory-leaks multithreading handles

在使用委托和.InvokeRequired时,在什么情况下从非UI线程更新UI控件会导致进程的句柄不断增加?

例如:

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

当在循环或定时器间隔中调用它时,程序的句柄会不断增加。

修改

如果上述内容被注释掉并进行了修改:

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

...然后句柄停止递增,但当然我不想允许跨线程调用。

编辑2:

以下是显示句柄增加的示例:

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;
}

8 个答案:

答案 0 :(得分:4)

我遇到了与

相同的问题
this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e);

每次调用创建一个句柄。

句柄递增,因为Invoke是同步的,并且有效地将句柄悬挂。

应使用Wait Handle处理结果或Asynchronous BeginInvoke方法,如下所示。

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

答案 1 :(得分:3)

Control.Invoke()方法不使用任何句柄。但是,这个代码显然是从一个线程中调用的。线程 使用句柄,其中5个。

Thread类没有Dispose()方法,尽管它应该有一个。这可能是设计的,对于线程池线程来说,很难可靠地调用,不可能这样。终结器释放线程所需的5个句柄。如果终结器永远不会运行,你的程序将需要越来越多的句柄。

没有让终结者跑步是很不寻常的。你必须有一个程序可以启动很多线程,但不会分配大量的内存。这往往只发生在静态测试中。您可以使用Perfmon.exe诊断此情况,使用.NET内存性能计数器并检查gen#0集合是否正在完成。

如果在生产程序中发生这种情况,那么您必须自己调用GC.Collect()以避免失控的句柄泄漏。

答案 2 :(得分:3)

我在代码中看到了同样的事情。我通过将Invoke替换为BeginInvoke来修复它。手柄泄漏消失了。

多伦。

答案 3 :(得分:0)

这是使用Invoke编组UI线程更新的标准模式。

您确定问题不是由您的应用中未包含在您的问题中的其他代码造成的吗?

答案 4 :(得分:0)

我不认为这是相关的。也许只是等待垃圾收集器在Invoke()中部署新分配的对象。

答案 5 :(得分:0)

我实际上看到了和JYelton一样的问题。我在一个线程内有相同的调用来更新UI。

一旦调用行someControl.Invoke(new DelegateUIUpdate(UIUpdate));,句柄就会增加一个。在调用时肯定存在某种泄漏,但我不知道是什么导致它。这已经在几个系统上得到验证。

答案 6 :(得分:0)

使用显式句柄终结的Aync调用。 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);
      }
    }
  }

请参阅http://msdn.microsoft.com/en-us/library/system.iasyncresult.asyncwaithandle.aspx注意:

  

当您使用委托的BeginInvoke方法异步调用方法并从生成的IAsyncResult获取等待句柄时,我们建议您在完成使用后立即关闭等待句柄,方法是调用WaitHandle.Close方法。如果您只是释放对等待句柄的所有引用,则在垃圾收集回收等待句柄时释放系统资源,但是当显式关闭或处置一次性对象时,垃圾收集工作效率更高。有关更多信息,请参阅AsyncResult.AsyncWaitHandle属性。

答案 7 :(得分:0)

这是一个扩展方法,其功能与普通的Invoke调用类似,但会在以下情况后清理句柄:

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();
        }
    }
}

然后你可以像普通的调用一样调用它:

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

它将阻塞并等待委托执行,然后在返回之前关闭句柄。