C# CurrentThreadTaskScheduler未完成同步
我尝试为视图模型编写一个单元测试,但在尝试验证ICommand两次调用异步方法时遇到了问题 我使用Moq作为我的依赖项。 我这样设置异步方法C# CurrentThreadTaskScheduler未完成同步,c#,unit-testing,task-parallel-library,async-await,C#,Unit Testing,Task Parallel Library,Async Await,我尝试为视图模型编写一个单元测试,但在尝试验证ICommand两次调用异步方法时遇到了问题 我使用Moq作为我的依赖项。 我这样设置异步方法 this.communicationServiceFake .Setup(x => x.WriteParameterAsync(It.IsAny<string>(), It.IsAny<object>())) .ReturnsAsyncIncomplete(); 基本上,CurrentThreadTask
this.communicationServiceFake
.Setup(x => x.WriteParameterAsync(It.IsAny<string>(), It.IsAny<object>()))
.ReturnsAsyncIncomplete();
基本上,CurrentThreadTaskScheduler来自这里:看起来确实是这样的:
public class CurrentThreadTaskScheduler : TaskScheduler
{
protected override void QueueTask(Task task)
{
this.TryExecuteTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool wasPreviouslyQueued)
{
return this.TryExecuteTask(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
yield break;
}
}
然后验证:
this.communicationServiceFake
.Verify(t => t.WriteParameterAsync("Parameter1", true), Times.Once);
this.communicationServiceFake
.Verify(t => t.WriteParameterAsync("Parameter2", true), Times.Once);
有时它说第二次呼叫没有发生。
如果我用ThreadSleep替换Task.Factory.StartNew以确保所有工作都已完成,那么即使不必要地延迟单元测试似乎是正确的,所有工作都会正常进行
为什么我的CurrentThreadTaskScheduler允许Task.Factory.StartNew在我的Command.Execute完成之前返回 中的
GetIncompleteTask
扩展方法使用Task.Run
,因此引入了另一个线程(和竞争条件)
如果所有任务都已完成,则CurrentThreadTaskScheduler
仅适用于async void
方法,这正是您使用ReturnsAsyncIncomplete
避免的
下面是正在发生的事情:
Execute
在CurrentThreadTaskScheduler
中运行。它等待线程池上运行的未完成任务。此时,StartNew
任务完成异步void
方法进行单元测试。这当然不容易
推荐解决方案
使用某种形式的async
-awareICommand
,如我在中所述。然后,您可以在单元测试中使用wait viewModel.Command.ExecuteAsync(null)
,而不必与自定义任务调度器混在一起
而且,异步模拟可以真正简化;不需要自定义等待程序,因为.NET framework已经有了一个:Task.Yield
。因此,您可以用一个扩展方法(未测试)替换所有代码:
另外,我建议您使用上面描述的
Task.Yield
方法,而不是定制的线程池。我似乎不明白为什么您需要使用StartNew
命令。Execute在后台有一些火和遗忘任务,如果我只使用viewModel.Command.Execute,这些任务还没有完成,这是一种尝试,通过客户任务调度器等待这些触发并忘记任务。遗憾的是,它并没有像预期的那样工作。感谢您提供了一个很好的答案,如果我答对了,IAsyncCommand只提供了一个解决方案,前提是我可以将视图模型中的类型更改为IAsyncCommand,否则我仍然在使用ICommand.Execute进行单元测试,它再次被实现为async void,对吗?
await this.communicationService.WriteParameterAsync("Parameter1", true);
await this.communicationService.WriteParameterAsync("Parameter2", true);
this.communicationServiceFake
.Verify(t => t.WriteParameterAsync("Parameter1", true), Times.Once);
this.communicationServiceFake
.Verify(t => t.WriteParameterAsync("Parameter2", true), Times.Once);
public static IReturnsResult<TMock> ReturnsIncompleteAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class
{
return mock.Returns(async () =>
{
await Task.Yield();
return value;
});
}
// Blocks the current thread until all continuations have completed.
AsyncContext.Run(() => viewModel.Command.Execute(null));