C# 在ReactiveUI中与TestScheduler的异步方法死锁

C# 在ReactiveUI中与TestScheduler的异步方法死锁,c#,reactiveui,C#,Reactiveui,我试图在测试中使用带有异步方法的reactiveui测试调度器 等待异步调用时,测试挂起 根本原因似乎是异步方法中等待的命令 [Fact] public async Task Test() => await new TestScheduler().With(async scheduler => { await SomeAsyncMethod(); // *** execution nev

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

编辑:

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

如何结合测试调度器执行异步调用

简言之

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

考虑到您对TestScheduler的兴趣,我认为您想测试一些涉及时间的东西。然而,从这一节:

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

因此,如果您想检查,例如,如果您的
任务
在100ms内完成,则必须等待异步方法完成。当然,这不是测试
TestScheduler
的目的

稍长的版本

TestScheduler
的目的是通过在特定时间点将事物置于运动状态并验证状态来验证工作流。由于我们只能在
TestScheduler
上操作时间,您通常不希望等待真正的异步代码完成,因为无法快速推进实际计算或I/O。请记住,这是关于验证工作流的:
vm.a
在20ms时有新值,因此
vm.B
应该在120ms时有新的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()
替换为在
scheduler
上创建的
var observable

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

参考:

2\您可以明确地引用
IScheduler

a) 使用由提供的调度程序

参考:

让我们用哈克德的另一句话来密切联系:

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


调用嵌套方法时是否尝试使用ConfigureWait(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();
        }

请尝试在所有异步方法上使用
.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();
}
测试问题是否与ConfigureWait相关的另一种方法是将项目移植到Asp.Net Core并在那里进行测试

Asp.net core不需要使用ConfigureWait来防止此阻塞问题


仅用于故障排除,当
SomeAsyncMethod
仅返回已完成的任务时会发生什么情况?试图缩小问题范围(分而治之)@Nkosi如果我将方法更改为非异步以返回已完成的任务,那么我就不能等待commandNo的执行。暂时忘记命令。我参考了
private Task SomeAsyncMethod(){return Task.CompletedTask;}
以查看它是否仍会像第一个代码段中暗示的那样挂起。@我明白了-它只在我等待命令时挂起,只是在等待
任务。延迟(100)
不会挂起,所以用async确认
是首选方法,(当您尝试使用异步时),我们现在需要检查
ReactiveCommand.CreateFromTask
以查看它是否在内部进行任何阻塞调用,从而导致死锁。这已经在另一个答案中指出,它仍然挂起。很遗憾,您可以再次检查我的答案吗?它不起作用,它与reactiveui TestSchedulerId有关。您没有看到吗这只是一个假设吗?你读了我编辑的答案了吗?@thumbmunkeys在深入挖掘之后,我选择修改我的帖子。
 [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();
        }
[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();
}