C# 在ReactiveUI中与TestScheduler的异步方法死锁
我试图在测试中使用带有异步方法的reactiveui测试调度器 等待异步调用时,测试挂起 根本原因似乎是异步方法中等待的命令C# 在ReactiveUI中与TestScheduler的异步方法死锁,c#,reactiveui,C#,Reactiveui,我试图在测试中使用带有异步方法的reactiveui测试调度器 等待异步调用时,测试挂起 根本原因似乎是异步方法中等待的命令 [Fact] public async Task Test() => await new TestScheduler().With(async scheduler => { await SomeAsyncMethod(); // *** execution nev
[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
很明显,上面的例子相当简单,但由于多个观察对象相互通知,这种测试可以提供有价值的见解,以及重构时的安全网
参考:
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();
}