Dispatcher.CurrentDispatcher导致ReactiveUI调用挂起

时间:2019-03-06 22:38:06

标签: c# winforms dispatcher reactiveui

在下面的示例中,在ReactiveCommand上调用.Execute()会挂起或创建死锁。为什么会发生这种情况?避免这种情况的最佳方法是什么?

仅在调用Dispatcher.CurrentDispatcher时发生错误。不幸的是,显而易见的答案是,在较大的项目中不是一个选择。

在项目中我有nuget包reactui-core和reactui-winforms,均为v7.4.0。我正在使用Resharper从Visual Studio运行nunit测试。

代码是NUnit测试装置,请注意,TimeoutAfterAsync是在超时后取消测试的辅助方法,无需使用该包装程序即可观察到行为

[TestFixture]
public class ReactiveCommandTests
{
    private static async Task<bool> ExecuteCommand()
    {
        await Task.Delay(1000);
        return true;
    }

    public static ReactiveCommand<Unit, bool> Command = ReactiveCommand.CreateFromTask(ExecuteCommand);
    public static ReactiveCommand<Unit, bool> CommandOnTaskpoolScheduler = ReactiveCommand.CreateFromTask(ExecuteCommand, outputScheduler: RxApp.TaskpoolScheduler);
    public static ReactiveCommand<Unit, bool> CommandAfterDispatcherInvoked = ReactiveCommand.CreateFromTask(ExecuteCommand);



    [Test, Order(1)]
    public async Task Test()
    {
        //THIS WORKS
        try
        {
            await TimeoutAfterAsync(
                Command.Execute(),
                TimeSpan.FromSeconds(5),
                "control");
        }
        catch (TimeoutException)
        {
            Assert.Fail("Control case timed out (not expected)");
        }
    }

    [Test, Order(2)]
    public async Task Test_CreateCommandAfterDispatcherCall()
    {
        //This line causes unwanted behaviour
        var x = Dispatcher.CurrentDispatcher;

        //THIS FAILS
        try
        {
            await TimeoutAfterAsync(
                CommandAfterDispatcherInvoked.Execute(),
                TimeSpan.FromSeconds(5),
                "after dispatcher creation");
        }
        catch (TimeoutException)
        {
            Assert.Fail("Executing commandAfterDispatcherInvoked timed out (expected, but not understood");
        }
    }

    [Test, Order(3)]
    public async Task Test_CreateCommandWithThreadpoolScheduler()
    {
        //This line causes unwanted behaviour
        var x = Dispatcher.CurrentDispatcher;

        //THIS WORKS AGAIN (using ThreadpoolScheduler when creating ReactiveCommand)
        try
        {
            await TimeoutAfterAsync(
                CommandOnTaskpoolScheduler.Execute(),
                TimeSpan.FromSeconds(5),
                "after dispatcher creation, with thread pool");
        }
        catch (TimeoutException)
        {
            Assert.Fail("ThreadpoolScheduler case timed out (not expected)");
        }
    }

    private static async Task<TResult> TimeoutAfterAsync<TResult>(IObservable<TResult> observable,
        TimeSpan timeout,
        string context)
    {
        var task = observable .ToTask();
        var result = await Task.WhenAny(task, Task.Delay(timeout));
        if (result == task)
        {
            // Task completed within timeout.
            return task.GetAwaiter().GetResult();
        }
        else
        {
            // Task timed out.
            throw new TimeoutException(context);
        }
    }
}

1 个答案:

答案 0 :(得分:1)

Dispatcher.CurrentDispatcher很有趣;如果当前线程还没有线程,它会为当前线程创建一个调度程序!这会导致单元测试出现问题,因为新的分派器是为不是STA且没有消息泵的线程池线程创建的。

理想的解决方案是不调用CurrentDispatcher。曾经使用awaitIProgress<T>或(如果需要)使用SynchronizationContext将结果/进度/事件传达给UI线程。这些抽象为创建测试环境要容易得多。

但是现在,您也许可以使用WpfContext,这是Async CTP早期版本中包含的旧实用程序类型。 WpfContext.Run将获取一个委托,为当前线程创建一个分配器上下文,并在该分配器上下文中执行该委托,并泵送其消息,直到异步操作完成为止。