【问题标题】:How to block the UI thread from another thread or force a form to run within the UI thread如何阻止另一个线程的 UI 线程或强制表单在 UI 线程中运行
【发布时间】:2008-12-16 20:27:38
【问题描述】:

我的应用程序的一个要求是,如果它失去数据库连接,那么它必须弹出一个大模式“无连接。稍后再试”对话框,阻止所有用户交互,直到重新连接。

我通过在应用程序开始时启动 DeviceMonitor 类的实例来实现这一点。此类创建一个 System.Threading.Timer 和每个 Tick(通常为 1 秒)以及它尝试从数据库中提取数据的一些其他内容。如果抽签失败并且确定原因是由于缺乏连接,则通过弹出上述对话框来处理异常。同样,如果数据获取成功并且对话框当前处于打开状态,则它会被强制关闭。

问题在于,尽管这一切正常,但 ConnectionLost 对话框并没有阻止用户与 UI 交互。这是有道理的,因为 Timer.Elapsed 事件在其自己的线程中引发,并且从回调中调用 noConnectionDialog.ShowDialog() 会阻塞它所在的线程,但不会阻塞 UI 线程。

据我了解,我需要强制 noConnectionDialog.ShowDialog() 在 UI 线程中运行或阻塞 UI 线程,直到调用 noConnectionDialog.Hide() 但我也不知道该怎么做。

也许还有其他补救措施,或者我在这里遗漏了一些东西。任何建议表示赞赏。

编辑: 更多信息 - 这是一个程式化的对话框,而不仅仅是一个消息框。它是在我的应用程序由 Castle Windsor 启动并注入到传递的 DialogFactory 类时创建的。因此,该对话框由

访问
var d = _dialogFactory.GetNoConnectionDialog();
d.ShowDialog();

我已经尝试将此代码放在计时器经过的回调之外 - 例如,当单击 UI 界面上的按钮时 - 它会从那里很好地阻止 UI,因此这与创建表单的位置无关。

【问题讨论】:

    标签: .net winforms multithreading


    【解决方案1】:

    我很确定 Marc 的建议应该可行。这就是我将如何编写它以使用您的对话框而不是MessageBox:

    someControl.Invoke((Action)delegate {
        var d = _dialogFactory.GetNoConnectionDialog();
        d.ShowDialog();
    }, null);
    

    如果这真的不起作用,我过去曾在我的表单上使用 Timer 控件 (System.Windows.Forms.Timer) 和一个带有 Tick 函数的操作队列,如下所示:

    void timer_Tick(object sender, System.EventArgs e)
    {
        lock(queue)
        {
            while(queue.Count > 0)
            {
                Action a = queue.Dequeue();
                a();
            }
        }
    }
    

    当您的 DeviceMonitor 类需要显示 UI 时,它会这样做:

    lock(queue)
    {
        queue.Enqueue((Action)delegate 
        {
            var d = _dialogFactory.GetNoConnectionDialog();
            d.ShowDialog();
        });
    }
    

    话虽如此,我真的想重申一下,我认为 Marc 的方法应该可以正常工作,如果您绝对确定 Control.Invoke 不适合您,我只会使用我的 Timer + queue 方法。

    【讨论】:

      【解决方案2】:

      如果您有权访问 UI 元素,则可以使用以下方式推送到 UI 线程:

      someControl.Invoke((Action)delegate {
          MessageBox.Show(someControl, "Foo");
          // could also show a form here, etc
      });
      

      (MessageBox.Show 中的someControl 有助于消息框的父级)

      如果您无权访问 UI 控件,也可以使用同步上下文:

      SynchronizationContext.Current.Post(delegate {
          MessageBox.Show("Foo");
      }, null);
      

      但保持控制更容易;-p

      【讨论】:

      • 这不是一个消息框。或者更确切地说,它是一个程式化的消息框,所以它必须是一个完整的形式。如果有必要,我确实有一个装饰器包装调用 Invoke() 的对话框,所以我认为它会在 UI 线程上运行,但显然不是。
      • 表单将要在创建它的线程上运行。所以在 Control.Invoke 中创建表单
      • 该表单是由 Castle Windsor 创建的,并且正在被 DI 到一个对话框工厂类中,所以我认为这不是问题。当我从设备监视器以外的地方调用它时,它会很好地阻止 UI 线程
      • System.Threading.SynchronizationContext.Current == null !!!这到底是什么意思?
      • 一个空的同步上下文意味着你没有一个UI-pumping线程,即一个主要的winform。
      【解决方案3】:

      这称为编组,是一个非常简单的概念,只要您阅读了一些关于它的好材料(Google 是您的朋友)。

      如果您的后台线程有一个委托调用 UI 线程拥有的对象,那么该方法(在委托的调用端)只需将自己编组回拥有其对象的线程(即将是 UI 线程)然后阻塞。这是非常简单的代码(IsInvokeRequired),您只需要了解如何布局即可。 (这基本上是在重申 Marc 所说的,但从更高的层面。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-21
        • 2017-07-26
        • 2019-07-11
        相关资源
        最近更新 更多