WPF 调度程序在 运行 UI 测试时从不执行操作
WPF dispatcher never executes action when running UI tests
我有一个内部使用 Dispatcher
异步更新 ObservableCollection
的 ViewModel。我想为该 ViewModel 编写一个单元测试,包括 使用 Dispatcher 的方法。
我使用在运行时注入的自定义 IDispatcher
抽象了调度程序。
这是我在正常模式下运行应用程序时使用的IDispatcher
实现
public class WPFDispatcher : IDispatcher
{
public void Invoke(Action action)
{
System.Windows.Application.Current.Dispatcher.Invoke(action);
}
}
ViewModel 像这样使用 IDispatcher
public async Task RefreshControls()
{
Parent.IsSoftBusy = true;
if (ControlsList == null)
ControlsList = new ObservableCollection<DataprepControl>();
await Task.Run(() =>
{
var updatedControls = _getControls();
Dispatcher.Invoke(() => _handleUpdatedControl(updatedControls));
});
Parent.IsSoftBusy = false;
}
从单元测试(Visual Studio 单元测试框架)切换到执行时,System.Windows.Application` 可能为空,因此我 在单元测试启动时
new System.Windows.Application();
这样做时,传递给 Invoke
的 Actions
永远不会执行 ,它们会无限期挂起。
如果我在 Invoke
调用上添加断点,我会看到 Dispatcher.Thread
是
- 在 STA 模式下(因此它可以处理 GUI 更新)
- 还活着(
IsAlive == true
)
- 处于
Background | WaitSleepJoin
状态(我不知道那是什么意思,但可能有用)
我不明白为什么这些操作没有排队。
请记住,我处于单元测试环境中,没有控件,所以我不能
Adding a static class that somehow tells the Dispatcher to consume its tasks没有效果
更改为 BeginInvoke
并不能解决问题。
假设在应用程序的 UI 线程中调用了视图模型方法,以下代码修改应该消除了使用 Dispatcher 的需要:
public async Task RefreshControls()
{
Parent.IsSoftBusy = true;
if (ControlsList == null)
{
ControlsList = new ObservableCollection<DataprepControl>();
}
var updatedControls = await Task.Run(() => _getControls());
_handleUpdatedControl(updatedControls);
Parent.IsSoftBusy = false;
}
正如另一个答案所述,最好使用 await
来使用后台操作的结果更新 UI,或者如果您需要多个更新,请使用 IProgress<T>
。
但在某些情况下这是不可能的,例如需要更新您的 UI 的外部因素。在这种情况下,您有一些更新来自后台线程,您确实需要将更新排队到 UI.
在这种情况下,我通常建议换行 SynchronizationContext
而不是 Dispatcher
。 API 更笨拙但更便携:SynchronizationContext
适用于所有 UI 平台。如果换行SynchronizationContext
,那么就可以使用AsyncContext
for testing.
如果您确实需要一个实际的 Dispatcher,那么创建一个 Dispatcher 实例是不够的:
Dispatcher.Thread is In STA mode (so it CAN handle GUI updates)
该线程已标记 用于 STA,但实际上成为 STA 线程的部分原因是它必须具有消息循环。因此,您需要一个实际的 STA 线程(即执行消息泵送的线程)来进行排队到 STA 的单元测试。对于这种情况下的测试,您应该使用 WpfContext
.
之类的东西
我有一个内部使用 Dispatcher
异步更新 ObservableCollection
的 ViewModel。我想为该 ViewModel 编写一个单元测试,包括 使用 Dispatcher 的方法。
我使用在运行时注入的自定义 IDispatcher
抽象了调度程序。
这是我在正常模式下运行应用程序时使用的IDispatcher
实现
public class WPFDispatcher : IDispatcher
{
public void Invoke(Action action)
{
System.Windows.Application.Current.Dispatcher.Invoke(action);
}
}
ViewModel 像这样使用 IDispatcher
public async Task RefreshControls()
{
Parent.IsSoftBusy = true;
if (ControlsList == null)
ControlsList = new ObservableCollection<DataprepControl>();
await Task.Run(() =>
{
var updatedControls = _getControls();
Dispatcher.Invoke(() => _handleUpdatedControl(updatedControls));
});
Parent.IsSoftBusy = false;
}
从单元测试(Visual Studio 单元测试框架)切换到执行时,System.Windows.Application` 可能为空,因此我
new System.Windows.Application();
这样做时,传递给 Invoke
的 Actions
永远不会执行 ,它们会无限期挂起。
如果我在 Invoke
调用上添加断点,我会看到 Dispatcher.Thread
是
- 在 STA 模式下(因此它可以处理 GUI 更新)
- 还活着(
IsAlive == true
) - 处于
Background | WaitSleepJoin
状态(我不知道那是什么意思,但可能有用)
我不明白为什么这些操作没有排队。
请记住,我处于单元测试环境中,没有控件,所以我不能
Adding a static class that somehow tells the Dispatcher to consume its tasks没有效果
更改为 BeginInvoke
并不能解决问题。
假设在应用程序的 UI 线程中调用了视图模型方法,以下代码修改应该消除了使用 Dispatcher 的需要:
public async Task RefreshControls()
{
Parent.IsSoftBusy = true;
if (ControlsList == null)
{
ControlsList = new ObservableCollection<DataprepControl>();
}
var updatedControls = await Task.Run(() => _getControls());
_handleUpdatedControl(updatedControls);
Parent.IsSoftBusy = false;
}
正如另一个答案所述,最好使用 await
来使用后台操作的结果更新 UI,或者如果您需要多个更新,请使用 IProgress<T>
。
但在某些情况下这是不可能的,例如需要更新您的 UI 的外部因素。在这种情况下,您有一些更新来自后台线程,您确实需要将更新排队到 UI.
在这种情况下,我通常建议换行 SynchronizationContext
而不是 Dispatcher
。 API 更笨拙但更便携:SynchronizationContext
适用于所有 UI 平台。如果换行SynchronizationContext
,那么就可以使用AsyncContext
for testing.
如果您确实需要一个实际的 Dispatcher,那么创建一个 Dispatcher 实例是不够的:
Dispatcher.Thread is In STA mode (so it CAN handle GUI updates)
该线程已标记 用于 STA,但实际上成为 STA 线程的部分原因是它必须具有消息循环。因此,您需要一个实际的 STA 线程(即执行消息泵送的线程)来进行排队到 STA 的单元测试。对于这种情况下的测试,您应该使用 WpfContext
.