Windows 窗体:UI 线程与 Show() 和 ShowDialog() 一起流动

Ily*_*huk 4 c# multithreading dialog .net-3.5 winforms

在 Windows 窗体上开发解决方案时,我养成了向用户展示持续进展的习惯。我实现了带有连续进度条的简单虚拟窗口:

连续进度窗口

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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

//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();
}
Run Code Online (Sandbox Code Playgroud)

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

让它按需要工作的最简单的解决方案是什么?

Han*_*ant 7

  continuousProgressWindow.ShowDialog(this);
  backgroundWorker.RunWorkerAsync();
Run Code Online (Sandbox Code Playgroud)

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

  backgroundWorker.RunWorkerAsync();
  continuousProgressWindow.ShowDialog(this);
Run Code Online (Sandbox Code Playgroud)

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

  this.BeginInvoke(new Action(() => backgroundWorker.RunWorkerAsync()));
  continuousProgressWindow.ShowDialog(this);
Run Code Online (Sandbox Code Playgroud)

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