如何从另一个线程关闭主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线程上调用它,使用:
_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线程上。