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

本文关键字:线程 UI 窗体 窗口 另一个 | 更新日期: 2023-09-27 18:17:12

我正在创建一个WPF MVVM应用程序。我有一个漫长的过程,我想在另一个线程中运行,同时显示一个繁忙的指标给用户。我的问题如下:

BusyIndicator控件的IsBusy属性绑定到我的视图模型的IsBusy公共属性,它实现了INotifyPropertyChanged接口。如果我用Join运行下面的代码,那么用户界面不会显示busy指示器,因为主UI线程正在等待线程"t"完成。如果我删除连接,那么托管WPF的Windows窗体过早关闭。我知道跨线程访问Windows窗体是一个很大的不不,但我所要做的就是关闭窗体,我认为最简单的解决方案是将_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();
    }

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

在这种情况下,最好的做法是在实际调用关闭表单之前,通知所有将要关闭的系统,这将使您有机会在最后运行任何进程。当您完成并想要从另一个线程关闭表单时,您需要在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,然后在完成时调用关闭和清理。

我建议你使用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();

RunWorker中的代码已经在UI线程中完成了。这个解决方案是非阻塞的,所以你可以看到isBusy的改变和UI的正确反应。DoWork部分在另一个线程中执行,但是如果您愿意,也可以使用ReportProgress功能。

这是我的建议。我会用。net Framework中包含的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线程上。