Windows 窗体:UI 线程使用 Show() 和 ShowDialog() 流动

Posted

技术标签:

【中文标题】Windows 窗体:UI 线程使用 Show() 和 ShowDialog() 流动【英文标题】:Windows Forms: UI threads flow with Show() and ShowDialog() 【发布时间】:2014-01-19 17:15:30 【问题描述】:

在 Windows 窗体上开发解决方案时,我进入了一个向用户显示持续进度的例程。我实现了带有连续进度条的简单虚拟窗口:

在解决方案树中,它与主窗口位于同一级别:

在做某事时显示持续进步的最简单的工作方法是以下代码。它确实有效

//This method works
private void DoSomeBackgroundStuffWithShow()

    ContinuousProgressWindow continuousProgressWindow = 
        new ContinuousProgressWindow();
    BackgroundWorker backgroundWorker = new BackgroundWorker();
    backgroundWorker.DoWork += (sender, arguments) =>
    
        //Do some  stuff for 4 seconds
        Thread.Sleep(4000);
    ;
    backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
    
        //Window is closed when needed. Great!
        continuousProgressWindow.Dispose(); 
    ;
    continuousProgressWindow.Show(this);
    backgroundWorker.RunWorkerAsync();

但我需要此窗口出现在最顶部并在工作时阻止其父级。以下代码非常相似,但它不起作用 - 显示对话框,但 从未关闭

//This method DOES NOT WORK
private void DoSomeBackgroundStuffWithShowDialog()

    ContinuousProgressWindow continuousProgressWindow =
        new ContinuousProgressWindow();
    BackgroundWorker backgroundWorker = new BackgroundWorker();
    backgroundWorker.DoWork += (sender, arguments) =>
    
        //Do some important stuff for 4 seconds
        Thread.Sleep(4000);
    ;
    backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
    
        //None of the following work for "ShowDialog() method"
        //Ran with debugger - breakpoints not hit!
        continuousProgressWindow.DialogResult = DialogResult.OK;
        continuousProgressWindow.Close();
        continuousProgressWindow.Dispose();
    ;
    continuousProgressWindow.ShowDialog(this);
    backgroundWorker.RunWorkerAsync();

然后,我意识到问题在于 UI 线程流:当进度窗口作为对话框运行时,MainWindow 线程被冻结,并且无法由RunWorkerCompleted 委托中的BackgroundWorker 调用以关闭对话框。

最简单的解决方案是什么?

【问题讨论】:

使用 showDialog,backgroundWorker.RunWorkerAsync(); 直到 continuousProgressWindow 退出时才会被调用。您是否尝试过在显示窗口之前启动backgroundWorker @StevenMills 我刚刚尝试过,它奏效了。发布带有编辑和注释代码的答案,我会接受。 等待想要接受有用的答案。太多的人只是读了答案就永远消失了。 【参考方案1】:

这里的问题是您在 backgroundWorker.RunWorkerAsync() 之前调用了 ContinuousProgressWindow.ShowDialog(this)。因此,一旦您关闭窗口,就会调用 backgroundWorker.RunWorkerAsync()。

我认为下面的代码应该可以工作,正如@Steven Mills 所建议的那样。

private void DoSomeBackgroundStuffWithShowDialog()

    ContinuousProgressWindow continuousProgressWindow =
        new ContinuousProgressWindow();
    BackgroundWorker backgroundWorker = new BackgroundWorker();
    backgroundWorker.DoWork += (sender, arguments) =>
    
        //Do some important stuff for 4 seconds
        Thread.Sleep(4000);
    ;
    backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
    
        //None of the following work for "ShowDialog() method"
        //Ran with debugger - breakpoints not hit!
        continuousProgressWindow.DialogResult = DialogResult.OK;
        continuousProgressWindow.Close();
        continuousProgressWindow.Dispose();
    ;

    backgroundWorker.RunWorkerAsync();
    continuousProgressWindow.ShowDialog(this);


【讨论】:

【参考方案2】:
  continuousProgressWindow.ShowDialog(this);
  backgroundWorker.RunWorkerAsync();

您遇到了一个简单的先有鸡还是先有蛋的问题,直到对话框关闭后 才启动worker。 ShowDialog() 是一个阻塞调用。所以 RunWorkerCompleted 事件不会触发,因为工人没有开始。最简单的解决方法是交换两个语句:

  backgroundWorker.RunWorkerAsync();
  continuousProgressWindow.ShowDialog(this);

这样做并不完全安全。这个 sn-p 不是问题,但在实际代码中存在工作人员在显示对话框之前完成 的危险。赔率低但不为零。为了解决这个问题,您希望延迟工作人员,直到您确定对话框已启动并正在运行。这可以通过对话框的 OnShown() 方法使用 Set() 的 AutoResetEvent 来完成。或者,更优雅的是,利用一个技巧:

  this.BeginInvoke(new Action(() => backgroundWorker.RunWorkerAsync()));
  continuousProgressWindow.ShowDialog(this);

Control.BeginInvoke() 的委托目标在程序重新进入消息循环时运行。这发生在对话框变得可见之后:)

【讨论】:

简短而有用的答案:经过测试,它可以正常工作。我在哪里可以阅读有关消息循环的这些技巧? 当然是在 *** 上 :) 这是一个古老的技巧,早在 .NET 出现之前就已经使用了。 C 程序员使用 PostMessage(),Charles Petzold 写过,C# 使代码更漂亮。

以上是关于Windows 窗体:UI 线程使用 Show() 和 ShowDialog() 流动的主要内容,如果未能解决你的问题,请参考以下文章

实现Winform 跨线程安全访问UI控件

在 C# 中使用多线程屏蔽/过滤图像(Windows 窗体应用程序)

JetBrains Rider C# | Windows 窗体 UI [关闭]

基础多线程更新窗体UI的若干方法

c#出现timer线程跑丢的情况,网上有说明需要线程重启来解决问题,请提供举例代码。

winform 利用 多线程 处理窗体假死,利用 Invoke BeginInvoke 处理子线程调用 UI 控件报错的问题