异步方法在ReactiveUI中使用TestScheduler进行死锁

thu*_*eys 12 c# reactiveui

我正在尝试在测试中使用带有异步方法的reactiveui测试调度程序.

等待异步调用时,测试挂起.

根本原因似乎是异步方法中等待的命令.

    [Fact]
    public async Task Test()
        => await new TestScheduler().With(async scheduler =>
        {
            await SomeAsyncMethod();

            // *** execution never gets here
            Debugger.Break();
        });

    private async Task SomeAsyncMethod()
    {
        var command = ReactiveCommand.CreateFromTask(async () =>
        {
            await Task.Delay(100);
        });

        // *** this hangs
        await command.Execute();
    }
Run Code Online (Sandbox Code Playgroud)

如何与没有死锁的测试调度程序一起执行异步调用?

我正在使用reactiveui 9.4.1

编辑:

我已经尝试了WithAsync()Funks答案中建议的方法,但行为是一样的.

Fun*_*unk 4

\n

如何与测试调度程序结合进行异步调用?

\n
\n\n

简而言之

\n\n

command.Execute()是一个冷可观察量。您需要订阅它,而不是使用await.

\n\n

鉴于您对 的兴趣TestScheduler,我认为您想测试一些涉及时间的东西。然而,从什么时候我应该关心日程安排部分来看:

\n\n
\n

通过“new Thread()”或“Task.Run”创建的线程无法在单元测试中控制。

\n
\n\n

因此,例如,如果您想检查是否Task在 100 毫秒内完成,则必须等到异步方法完成。可以肯定的是,这不是那种测试TestScheduler目的。

\n\n

稍微长一点的版本

\n\n

目的TestScheduler是通过启动事物并验证特定时间点的状态来验证工作流程。由于我们只能操作 a 上的时间TestScheduler,因此您通常不希望等待真正的异步代码完成,因为无法快进实际计算或 I/O。请记住,它是关于验证工作流程:vm.A在 20 毫秒时有新值,因此vm.B应该在 120 毫秒时有新值,...

\n\n

那么如何测试 SUT?

\n\n

1\\ 您可以使用以下方式模拟异步方法scheduler.CreateColdObservable

\n\n
public class ViewModelTests\n{\n    [Fact]\n    public void Test()\n    {\n        string observed = "";\n\n        new TestScheduler().With(scheduler =>\n        {\n            var observable = scheduler.CreateColdObservable(\n                scheduler.OnNextAt(100, "Done"));\n\n            observable.Subscribe(value => observed = value);\n            Assert.Equal("", observed);\n\n            scheduler.AdvanceByMs(99);\n            Assert.Equal("", observed);\n\n            scheduler.AdvanceByMs(1);\n            Assert.Equal("Done", observed);\n        });\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里我们基本上替换command.Execute()var observablecreated on scheduler

\n\n

很明显,上面的示例相当简单,但是通过多个可观察量相互通知,这种测试可以提供有价值的见解,并在重构时提供安全网。

\n\n

参考:

\n\n\n\n

IScheduler2\\ 您可以明确引用

\n\n

a) 使用RxApp提供的调度器

\n\n
public class MyViewModel : ReactiveObject\n{\n    public string Observed { get; set; }\n\n    public MyViewModel()\n    {\n        Observed = "";\n\n        this.MyCommand = ReactiveCommand\n            .CreateFromTask(SomeAsyncMethod);\n    }\n\n    public ReactiveCommand<Unit, Unit> MyCommand { get; }\n\n    private async Task SomeAsyncMethod()\n    {\n        await RxApp.TaskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));\n        Observed = "Done";\n    }\n}\n\npublic class ViewModelTests\n{\n    [Fact]\n    public void Test()\n    {\n        new TestScheduler().With(scheduler =>\n        {\n            var vm = new MyViewModel();\n\n            vm.MyCommand.Execute().Subscribe();\n            Assert.Equal("", vm.Observed);\n\n            scheduler.AdvanceByMs(99);\n            Assert.Equal("", vm.Observed);\n\n            scheduler.AdvanceByMs(1);\n            Assert.Equal("Done", vm.Observed);\n        });\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

笔记

\n\n
    \n
  • CreateFromTask创建一个ReactiveCommand具有异步执行逻辑的。无需将该Test方法定义为异步或等待TestScheduler.

  • \n
  • With在扩展方法的范围内RxApp.TaskpoolScheduler== RxApp.MainThreadScheduler.new TestScheduler()

  • \n
\n\n

b) 通过构造函数注入管理您自己的调度程序

\n\n
public class MyViewModel : ReactiveObject\n{\n    private readonly IScheduler _taskpoolScheduler;\n    public string Observed { get; set; }\n\n    public MyViewModel(IScheduler scheduler)\n    {\n        _taskpoolScheduler = scheduler;\n        Observed = "";\n\n        this.MyCommand = ReactiveCommand\n            .CreateFromTask(SomeAsyncMethod);\n    }\n\n    public ReactiveCommand<Unit, Unit> MyCommand { get; }\n\n    private async Task SomeAsyncMethod()\n    {\n        await _taskpoolScheduler.Sleep(TimeSpan.FromMilliseconds(100));\n        Observed = "Done";\n    }\n}\n\npublic class ViewModelTests\n{\n    [Fact]\n    public void Test()\n    {\n        new TestScheduler().With(scheduler =>\n        {\n            var vm = new MyViewModel(scheduler); ;\n\n            vm.MyCommand.Execute().Subscribe();\n            Assert.Equal("", vm.Observed);\n\n            scheduler.AdvanceByMs(99);\n            Assert.Equal("", vm.Observed);\n\n            scheduler.AdvanceByMs(0);\n            Assert.Equal("Done", vm.Observed);\n        });\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

参考:

\n\n\n\n

让我们引用来自 Haacked 的另一句话:

\n\n
\n

不幸的是,下一点很重要,它TestScheduler不会扩展到现实生活中,因此您的恶作剧仅限于异步响应式代码。因此,如果您调用Thread.Sleep(1000)测试,该线程实际上将被阻塞一秒钟。但就测试调度程序而言,时间还没有过去。

\n
\n