C# UI线程.Invoke()导致句柄泄漏?

C# UI线程.Invoke()导致句柄泄漏?,c#,memory-leaks,multithreading,handles,C#,Memory Leaks,Multithreading,Handles,在什么情况下,当使用委托和时,从非UI线程更新UI控件会导致进程的句柄不断增加 例如: public delegate void DelegateUIUpdate(); private void UIUpdate() { if (someControl.InvokeRequired) { someControl.Invoke(new DelegateUIUpdate(UIUpdate)); return; } // do somet

在什么情况下,当使用委托和
时,从非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;
}

这是使用
Invoke
对UI线程进行更新的标准模式


您确定您的问题不是由您的问题中未包含的应用程序中的其他代码引起的吗?

我认为这与此无关。可能只是等待垃圾收集器在Invoke()内处理新分配的对象。

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

Thread类没有Dispose()方法,尽管它应该有一个。这可能是出于设计,很难可靠地调用线程池线程,这是不可能的。线程所需的5个句柄由终结器释放。如果终结器从未运行,您的程序将需要不断增加的句柄数

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


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

我实际上看到了与JYelton相同的问题。我在线程中有相同的调用来更新UI


只要行
someControl.Invoke(newdelegateuiupdate(UIUpdate))时,句柄增加1。调用上肯定存在某种泄漏,但我不知道是什么导致了它。这已在多个系统上得到验证。

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


多伦。

我也有同样的问题

this.Invoke(new DelegateClockUpdate(ChangeClock), sender, e);
为每个调用创建一个句柄

句柄会增加,因为调用是同步的,并且实际上句柄一直挂起

应使用等待句柄或异步BeginInvoke方法来处理结果,如下所示

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

带有显式句柄finalize的Aync调用。示例:

  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);
      }
    }
  }
公共静态类ActionExtensions
{
private static readonly ILog log=LogManager.GetLogger(typeof(ActionExtensions));
/// 
///异步执行操作。
/// 
///行动。
公共静态无效异步调用句柄(
本行动(行动)
{
if(action==null)
{
返回;
}
foreach(Action.GetInvocationList()中的操作处理程序)
{
//启动异步调用。包括异步回调
//代表回调方法和数据的委托
//需要调用EndInvoke。
handler.BeginInvoke(
ar=>
{
尝试
{
//检索委托。
var handlerToFinalize=(Action)ar.AsyncState;
//调用EndInvoke释放资源。
EndInvoke(ar);
var handle=ar.AsyncWaitHandle;
if(handle.SafeWaitHandle!=null&&!handle.SafeWaitHandle.IsInvalid&&!handle.SafeWaitHandle.IsClosed)
{
((IDisposable)句柄).Dispose();
}
}
捕获(异常)
{
log.Error(“异步操作执行错误”,异常);
}
},
经办人);
}
}
}
见注:

当您使用委托的BeginInvoke方法异步调用方法并从结果IAsyncResult中获取等待句柄时,建议您在使用完等待句柄后立即通过调用WaitHandle.close方法关闭它。如果您只是释放对等待句柄的所有引用,则当垃圾收集回收等待句柄时,系统资源将被释放,但当明确关闭或处置一次性对象时,垃圾收集的工作效率更高。有关详细信息,请参阅AsyncResult.AsyncWaitHandle属性


下面是一个扩展方法,它的功能类似于正常的调用,但会在以下情况下清理句柄:

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

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

我过去使用过描述的模式来更新UI组件,没有句柄增加问题,但在当前项目中,这肯定与线程到UI调用有关。如果我注释掉检查和调用委托的部分,而不是
CheckForIllegalCrossThreadCalls=false然后句柄停止增加,尽管我不想让它保持这种状态。我会用这些信息更新这个问题。好吧,这很难解释。让我们了解代码的其余部分是做什么的,以及在什么特定语句中看到句柄计数增加。添加了一个更详细地显示问题的示例。表单上的一个按钮触发更新。这里还有一些关于这个问题的有用讨论:调用
GC.Collect()
实际上会阻止句柄的增加。有趣的是,如果
GC.Collect()