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();

这样做时,传递给 InvokeActions 永远不会执行 ,它们会无限期挂起。

如果我在 Invoke 调用上添加断点,我会看到 Dispatcher.Thread

我不明白为什么这些操作没有排队。

请记住,我处于单元测试环境中,没有控件,所以我不能

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.

之类的东西