在我的 WPF Window_Loaded事件处理程序中,我有类似以下内容:
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
// load data from database
this.Dispatcher.Invoke((Action)delegate
{
// update UI with loaded data
});
});
我想知道的是,当用户从数据库中加载数据并且在this.Dispatcher.Invoke例程运行之前,用户关闭表单时会发生什么?
会抛出ObjectDisposedException吗?还是Dispatcher会忽略Invoke例程(在处理窗口时)?
我已经尝试通过一些基本测试来弄清楚自己的状况,但是到目前为止,我的结果是没有发生任何不良情况。没有异常被抛出,应用程序也不会崩溃。
但是,当我使用ThreadPool进行相同的操作时,我曾经历过几次糟糕的经历。在Window_Loaded事件处理程序中,我将新的用户工作项排队到ThreadPool中,而在加载数据时,我按下Esc键(我有一个Window_KeyUp事件处理程序监听Esc键,如果按下了它,则称为this.Close( )),并且由于窗口已经关闭,因此尝试更新UI(在Dispatcher.Invoke内部)时,应用程序崩溃了。
由于Task库在后台使用了ThreadPool,因此除非我编写代码来保护应用程序,否则恐怕还会再次发生这种情况。
让我们稍作更改-在Dispatcher.Invoke例程中更新UI时,用户关闭表单会发生什么?
表单关闭会推迟到Invoke方法返回之前吗?还是可以抛出一些异常?
如果有可能引发异常,如何最好地处理它们?
我能想到的最好的办法是让 bool 值readyToClose = false;连接一个Window_Closing事件处理程序,该处理程序检查(!readyToClose)e.Cancel = true;然后,在更新我的UI后,可以将readyToClose = true设置为,因此,如果用户尝试过早关闭表单,它将被取消。
还是应该改用try {...} catch(ObjectDisposedException){//什么都不做?
最佳答案
在Windows窗体中,如果处置了该控件(例如,用户关闭了窗体),则确实会抛出在某些控件(例如您的主窗体)上调用Invoke
的情况。一种简单的避免方法是使用winforms SynchronizationContext
类。之所以有效,是因为WindowsFormsSynchronizationContext
保留了自己的内部控件,在该控件上调用了Invoke
/BeginInvoke
命令。见Exploiting the BackGroundWorker for cross-thread invocation of GUI actions on Winforms controls?
在WPF中,SynchronizationContext
委托(delegate)给调度程序,因此使用它们中的任何一个都是相同的。但是,由于WPF调度程序不像控件那样是一次性的(尽管可以关闭),因此您不必担心ObjectDisposedException
。但是,我相信如果调度程序已关闭,则调用Invoke
可能会挂起您的应用程序(因为它将等待永远无法完成的操作)-而是调用BeginInvoke
来解决这一问题。话虽这么说,ThreadPool线程(这是默认的Task Scheduler创建的线程,就像上面的例子一样)是Background线程,即使它们卡在Dispatcher.Invoke
调用上,也不会阻止您的进程退出。
简而言之,在Windows窗体和WPF中,使用SynchronizationContext.Post
(在WPF中等效于Dispatcher.BeginInvoke
)将解决您正在讨论的一般问题。
让我们稍作更改-在Dispatcher.Invoke例程中更新UI时,用户关闭表单会发生什么?
不会发生这种情况-Dispatcher.Invoke
调用正在运行时,UI线程正忙,特别是它无法处理用户需要关闭表单的键盘或鼠标单击之类的用户输入。
关于c# - 当任务正在运行且其窗口关闭时会发生什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5562969/