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

时间:2014-01-02 09:30:36

标签: c# multithreading winforms dialog .net-3.5

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

Continuos progress window

在解决方案树中,它与主窗口

位于同一级别

Windows forms solution tree

在执行某些操作时显示持续进度的最简单的工作方法是以下代码。它可行

//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();
}

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

//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线程被冻结,而BackgroundWorker中的RunWorkerCompleted无法调用它委托关闭对话框。

使其按预期工作的最简单的解决方案是什么?

2 个答案:

答案 0 :(得分:4)

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

你有一个简单的鸡蛋问题,你不会在关闭对话框后启动工作人员。 ShowDialog()是一个阻塞调用。因此,RunWorkerCompleted事件不会触发,因为worker没有启动。最简单的解决方法是交换两个语句:

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

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

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

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

答案 1 :(得分:2)

这里的问题是你在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);

}