代码之家  ›  专栏  ›  技术社区  ›  thumbmunkeys

reactiveui中testscheduler的异步方法死锁

  •  12
  • thumbmunkeys  · 技术社区  · 7 年前

    我试图在测试中使用带有异步方法的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();
        }
    

    如何将异步调用与不死锁的测试计划程序结合使用?

    我正在使用ReactiveUI 9.4.1

    编辑:

    我试过 WithAsync() 方法正如funks-answer所建议的,但行为是相同的。

    3 回复  |  直到 7 年前
        1
  •  3
  •   Funk    7 年前

    如何结合测试计划程序进行异步调用?

    简而言之

    command.Execute() 是可以观察到的寒冷。你需要订阅它,而不是使用 await 是的。

    考虑到你对 TestScheduler ,我认为你想测试一些涉及时间的东西。但是,从 When should I care about scheduling 章节:

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

    所以,如果你想检查,例如 Task 在100毫秒内完成,必须等到异步方法完成。当然,那是 不 这种测试 测试计划程序 是命中注定的。

    稍长的版本

    目的 测试计划程序 是通过在某个时间点将事物置于活动状态和验证状态来验证工作流。因为我们只能在 测试计划程序 ,您通常不希望等待真正的异步代码完成,因为无法快速转发实际计算或I/O。请记住,这是关于验证工作流的: vm.A 在20毫秒时有新值,所以 vm.B 在120毫秒时应该有新的VAL,…

    那你怎么能测试SUT呢?

    1\n可以使用 scheduler.CreateColdObservable

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

    在这里我们基本上取代了 command.execute() 具有 var observable 创建于 scheduler 是的。

    很明显上面的例子相当简单,但是有几个观察结果相互通知,这种测试可以提供有价值的见解,以及重构时的安全网。

    裁判:

    2.您可以引用 IScheduler 明确地

    a)使用由 RxApp

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

    注意

    • CreateFromTask 创建 ReactiveCommand 使用异步执行逻辑。不需要定义 Test 方法为异步或等待 测试计划程序 是的。

    • 内 With 可拓方法的范围 RxApp.TaskpoolScheduler = RxApp.MainThreadScheduler = new TestScheduler() 是的。

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

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

    裁判:

    让我们用haacked的另一句话来接近:

    不幸的是,下一点很重要, 测试计划程序 不会延伸到现实生活中,所以你的恶作剧仅限于异步反应代码。因此,如果你打电话 Thread.Sleep(1000) 在您的测试中,该线程将真正被阻塞一秒钟。但就测试调度程序而言,没有时间过去。

        2
  •  1
  •   MistyK    7 年前

    调用嵌套方法时是否尝试使用configureawait(false)?

     [Fact]
        public async Task Test()
            => await new TestScheduler().With(async scheduler =>
            {
                // this hangs
                await SomeAsyncMethod().ConfigureAwait(false);
    
                // ***** execution will never get to here
                Debugger.Break();
            }
    
        3
  •  0
  •   Derviş Kayımbaşıoğlu    7 年前

    请尝试使用 .ConfigureAwait(false) 在所有异步方法上。 这将为您提供非阻塞行为。

    [Fact]
    public async Task Test()
        => await new TestScheduler().With(async scheduler =>
        {
            await SomeAsyncMethod().ConfigureAwait(false);
    
            // *** execution never gets here
            Debugger.Break();
        }).ConfigureAwait(false);
    
    private async Task SomeAsyncMethod()
    {
        var command = ReactiveCommand.CreateFromTask(async () =>
        {
            await Task.Delay(100).ConfigureAwait(false);
        }).ConfigureAwait(false);
    
        // *** this hangs
        await command.Execute();
    }
    

    测试问题是否与configureawait相关的另一种方法是将项目移植到asp.net核心并在那里进行测试。

    ASP.NET核心不需要使用configureawait来防止此阻止问题。

    Check this for Reference