如何从另一个线程关闭主UI线程上的Windows窗体

3
我正在创建一个WPF MVVM应用程序。我有一个长时间运行的进程,我希望在显示忙碌指示器给用户的同时在另一个线程中运行。我遇到的问题如下:
BusyIndicator控件的IsBusy属性绑定到实现INotifyPropertyChanged接口的我的视图模型的IsBusy公共属性。如果我使用Join运行下面的代码,则主UI线程等待线程“t”完成时,用户界面不会显示忙碌指示器。如果我删除join,则托管WPF的Windows表单关闭得太早。我知道跨线程访问Windows Forms是不可取的,但由于我只想关闭窗体,所以我认为最简单的解决方案是将_hostForm.Close()移动到“DoLongProcess”方法的末尾。当然,如果我这样做,就会出现跨线程异常。请建议在这种情况下采取的最佳方法。
<extToolkit:BusyIndicator IsBusy="{Binding Path=IsBusy}" >
    <!-- Some controls here -->
</extToolkit:BusyIndicator>

private void DoSomethingInteresting() { 

        //  Set the IsBusy property to true which fires the 
        //  notify property changed event
        IsBusy = true;

        //  Do something that takes a long time
        Thread t = new Thread(DoLongProcess);
        t.Start();
        t.Join();

        //  We're done. Close the Windows Form
        IsBusy = false;
        _hostForm.Close();

    }

3
https://dev59.com/_kfSa4cB1Zd3GeqPAtph#947482 - undefined
3个回答

8
在这种情况下,最好的做法是在实际调用表单关闭之前,通知所有系统你将要关闭,这将为你提供在结束时运行任何进程的机会。当你完成并想要从另一个线程关闭表单时,你需要使用以下方式在UI线程上调用它:
_hostForm.BeginInvoke(new Action(() => _hostForm.Close()));

如果您经常需要从另一个线程关闭表单,则最好创建一个线程安全版本的close方法,即:

public class MyForm : Form
{
    // ...

    public void SafeClose()
    {
        // Make sure we're running on the UI thread
        if (this.InvokeRequired)
        {
            BeginInvoke(new Action(SafeClose));
            return;
        }

        // Close the form now that we're running on the UI thread
        Close();
    }

    // ...
}

采用这种方法,您可以在运行异步操作的同时继续更新表单及其UI, 然后在完成时调用关闭和清理。


谢谢大家的回答。我选择了这个答案,因为它很好,并且适用于我没有明确要求使用的.NET 3.5。其他人的回答也值得赞扬。再次感谢大家。 - undefined

3

我建议你使用BackgroundWorker类。按照以下示例操作:

BackgroundWorker wrk = new BackgroundWorker();
            wrk.WorkerReportsProgress = true;
            wrk.DoWork += (a, b) =>
            {
                ... your complex stuff here
            };

            wrk.RunWorkerCompleted += (s, e) =>
                {
                   isBusy=false;
                   _hostForm.Close();
                };
            wrk.RunWorkerAsync();

RunWorkerComplete事件中的代码已在UI线程上运行。这个解决方案是非阻塞的,所以您可以看到isBusy已经改变,并且UI正确地做出了反应。DoWork部分在另一个线程中执行,但如果需要,您也可以使用ReportProgress功能。


3

以下是我的建议。我建议您使用.NET Framework自版本4以来包含的TPL中的任务(Tasks)来解决此问题:

private void DoSomethingInteresting() 
{
    IsBusy = true;

    var task = new Task(() => DoLongProcess());
    task.ContinueWith(previousTask =>
                      {
                          IsBusy = false;
                          _hostForm.Close();
                      }, TaskScheduler.FromCurrentSynchronizationContext());

    task.Start();
} 

编辑

解释:该工作在后台的一个任务中完成。当此任务完成时,第二个任务 .ContinueWith... 会自动启动,并由于 TaskScheduler.FromCurrentSynchronizationContext() 在UI线程上运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接