8

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

例如:

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

8 回答 8

4

我有同样的问题

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

每次调用创建一个句柄。

句柄递增,因为 Invoke 是同步的,并且实际上句柄已挂起。

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

this.BeginInvoke(new DelegateClockUpdate(ChangeClock), sender, e);    
于 2011-10-22T11:33:28.983 回答
3

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

Thread 类没有 Dispose() 方法,尽管它应该有一个。这可能是设计使然,对于线程池线程来说,可靠地调用是非常困难的,不可能如此。线程需要的 5 个句柄由终结器释放。如果终结器从不运行,您的程序将需要越来越多的句柄。

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

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

于 2010-06-15T20:10:49.190 回答
3

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

多伦。

于 2011-08-03T12:29:05.690 回答
1

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

一旦someControl.Invoke(new DelegateUIUpdate(UIUpdate));调用该行,句柄就会增加一。调用肯定存在某种泄漏,但我不知道是什么原因造成的。这已在多个系统上得到验证。

于 2010-06-15T20:16:14.447 回答
1

具有显式句柄 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);
      }
    }
  }

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

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

于 2014-04-03T12:44:43.410 回答
1

这是一个扩展方法,其功能类似于正常的 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";
});

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

于 2015-07-21T01:35:01.600 回答
0

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

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

于 2010-06-15T19:45:05.227 回答
0

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

于 2010-06-15T19:45:28.577 回答