【问题标题】:Windows Forms: Is there a way to wait for all pending Invokes to a Control to end?Windows 窗体:有没有办法等待对控件的所有挂起调用结束?
【发布时间】:2011-09-22 19:55:06
【问题描述】:

我需要这样做才能解决死锁。 我的 Windows 窗体控件引用了包含 C++ 本机类的 C++/CLI 类。本机类对 C++/CLI 类进行回调,将它们映射到表单处理的事件。这些回调是从一直运行的线程中调用的。

当我想释放控件时,我取消注册所有事件,这样原生类就不能再回调了。完成后,我会处理 C++/CLI 包装器,这反过来会破坏本机类。在本机类析构函数中,我使用 Windows 事件通知线程结束,并无限期地等待线程结束。

但是,如果线程在处理开始时处于回调的中间,他可能会在Control.Invoke 中停止,然后出现死锁。因此标题问题。这可能吗?如果是这样,我可以这样做:

  • 取消注册所有事件(本机线程将无法再回调)
  • 等待所有待处理的 .Invokes 完成
  • 处置 C++/CLI 包装器
    • 销毁 C++ 原生类
      • 结束信号线程
      • 等待线程结束(不能与 Invoke 死锁,因为所有这些都已完成,并且由于事件未注册,因此无法再触发它们)
  • 幸福

我愿意接受其他解决此问题的建议

【问题讨论】:

    标签: .net winforms deadlock invoke dispose


    【解决方案1】:

    选项 1:在删除事件处理程序之后但在析构函数之前通过 Application.DoEvents 处理挂起的回调。请注意,文档说这会处理 Windows 消息,但没有提及这是否会强制处理 Invokes。

    选项 2:不要阻塞事件线程:通过 BeginInvoke 调用表单回调。请注意,您可能会在 C++ 对象被销毁后收到事件,因此向事件处理程序添加额外的逻辑以确保 C++ 对象没有被销毁。

    仅供参考,BeginInvokeInvoke 都使用此功能。 BeginInvokesynchronous 设置为 false。请注意,我稍微修改了这个方法以删除不感兴趣的部分:

    private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
    {
        ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
        lock (this.threadCallbackList)
        {
            if (threadCallbackMessage == 0)
                threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
            this.threadCallbackList.Enqueue(entry);
        }
        UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
    
        // BeginInvoke just takes this branch instead of waiting for completion
        if (!synchronous)
            return entry;
        if (!entry.IsCompleted)
            this.WaitForWaitHandle(entry.AsyncWaitHandle);
        if (entry.exception != null)
            throw entry.exception;
        return entry.retVal;
    }
    

    【讨论】:

    • +1 我写的是一样的,但在我的版本中 BeginInvoke 是选项 1 :)
    • +1。我必须使用Invoke,因为这些回调需要尽快更新 GUI(我知道 .BeginInvoke 推迟了 GUI 更新)。我会尝试选项 1,看看效果如何
    • BeginInvoke 不会推迟。在 GUI 更新之前,它不会阻塞发送线程。调用仍然会以同样快的速度发生。
    • @Dark Falcon:但是...如果我调用Invoke,控制权将尽快转移到GUI线程,如果不是立即,不是吗?我被告知关键的 GUI 更新必须使用 Invoke
    • 没有。该事件将被添加到队列中,并且 GUI 线程将在下次开始等待新事件时处理该队列。 BeginInvoke 做同样的事情,只是不等待项目被执行。
    【解决方案2】:

    不可能响应表单关闭事件并等待线程完成。存在无法解决的线程竞争条件。并且非常有可能发生死锁,DoEvents 会打破它但太丑陋了。

    实现 FormClosing 事件并告诉类停止运行其线程。并取消关闭。线程应该在它退出之前引发一个事件。在事件处理程序中使用 BeginInvoke() 将该调用编组到主线程。

    现在您可以保证所有调用都已完成,因为它们是有序的。此外,您可以保证线程不能再生成任何事件,因此不会有更多调用到来,并且取消订阅事件是安全的(实际上不是必需的)。设置一个现在可以真正关闭的标志并调用 Close() 来实际关闭表单。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-25
      • 2015-11-15
      • 1970-01-01
      相关资源
      最近更新 更多