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

时间:2011-12-09 09:40:08

标签: c# wpf multithreading

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

BusyIndi​​cator控件的IsBusy属性绑定到我的视图模型的IsBusy公共属性,该属性实现了INotifyPropertyChanged接口。如果我使用Join运行下面的代码,那么用户界面不会显示忙指示符,因为主UI线程正在等待线程“t”完成。如果我删除了连接,那么托管WPF的Windows窗体会过早关闭。我知道跨线程访问Windows窗体是一个很大的问题,但是我想要做的就是关闭Form I,最简单的解决方案是将_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 个答案:

答案 0 :(得分: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,然后在完成后调用关闭和清理。

答案 1 :(得分: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();

RunWorker complete中的代码已经在UI线程上。此解决方案是非阻塞的,因此您可以看到isBusy已更改且UI正常响应。 DoWork部分在另一个线程中执行,但如果需要,您也可以使用ReportProgress功能。

答案 2 :(得分:2)

这是我对你的建议。我将使用{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线程上运行。