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

发布于 2024-09-05 23:19:15 字数 1711 浏览 3 评论 0原文

使用委托和 .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;
}

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

花心好男孩 2024-09-12 23:19:15

时遇到了同样的问题

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

我在每次调用创建一个句柄

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

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

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

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);    
一曲琵琶半遮面シ 2024-09-12 23:19:15

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

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

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

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

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.

如歌彻婉言 2024-09-12 23:19:15

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

多伦.

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

Doron.

≈。彩虹 2024-09-12 23:19:15

我实际上看到与 JYelton 发生同样的问题。我在线程内进行了相同的调用来更新 UI。

一旦调用 someControl.Invoke(new DelegateUIUpdate(UIUpdate)); 行,句柄就会增加 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.

冷情妓 2024-09-12 23:19:15

具有显式句柄终结的 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 属性。

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.

决绝 2024-09-12 23:19:15

这是一个扩展方法,其功能与普通 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";
});

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

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.

暗喜 2024-09-12 23:19:15

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

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

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?

等风也等你 2024-09-12 23:19:15

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文