我刚看过just的一个视频类,他在其中谈到了单元测试异步方法。这是在一个付费网站上,但我在他的书中发现了类似他所说的东西(只有Ctrl+F "15.6.3.单元测试异步代码“)。
完整的代码可以在他的集散地上找到,但是为了我的问题,我已经简化了它(我的代码基本上是StockBrokerTest.CalculateNetWorthAsync_AuthenticationFailure_ThrowsDelayed(),但是有TimeMachine和高级程序操作内联)。
让我们假设我们有一个类来测试失败的登录(没有单元测试框架来简化这个问题):
public static class LoginTest
{
private static TaskCompletionSource<Guid?> loginPromise = new TaskCompletionSource<Guid?>();
public static void Main()
{
Console.WriteLine("== START ==");
// Set up
var context = new ManuallyPumpedSynchronizationContext(); // Comment this
SynchronizationContext.SetSynchronizationContext(context); // Comment this
// Run method under test
var result = MethodToBeTested();
Debug.Assert(!result.IsCompleted, "Result should not have been completed yet.");
// Advancing time
Console.WriteLine("Before advance");
loginPromise.SetResult(null);
context.PumpAll(); // Comment this
Console.WriteLine("After advance");
// Check result
Debug.Assert(result.IsFaulted, "Result should have been faulted.");
Debug.Assert(result.Exception.InnerException.GetType() == typeof(ArgumentException), $"The exception should have been of type {nameof(ArgumentException)}.");
Console.WriteLine("== END ==");
Console.ReadLine();
}
private static async Task<int> MethodToBeTested()
{
Console.WriteLine("Before login");
var userId = await Login();
Console.WriteLine("After login");
if (userId == null)
{
throw new ArgumentException("Bad username or password");
}
return userId.GetHashCode();
}
private static Task<Guid?> Login()
{
return loginPromise.Task;
}
}其中,ManuallyPumpedSynchronizationContext的实现是:
public sealed class ManuallyPumpedSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<Tuple<SendOrPostCallback, object>> callbacks;
public ManuallyPumpedSynchronizationContext()
{
callbacks = new BlockingCollection<Tuple<SendOrPostCallback, object>>();
}
public override void Post(SendOrPostCallback callback, object state)
{
Console.WriteLine("Post()");
callbacks.Add(Tuple.Create(callback, state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("Synchronous operations not supported on ManuallyPumpedSynchronizationContext");
}
public void PumpAll()
{
Tuple<SendOrPostCallback, object> callback;
while(callbacks.TryTake(out callback))
{
Console.WriteLine("PumpAll()");
callback.Item1(callback.Item2);
}
}
}产出如下:
== START ==
Before login
Before advance
After login
After advance
== END ==我的问题是:为什么我们需要ManuallyPumpedSynchronizationContext
为什么默认的SynchronizationContext还不够呢?Post()方法甚至没有被调用(基于输出)。我已经尝试过注释用// Comment this标记的行,输出是相同的,断言是通过的。
如果我正确理解Jon在视频中说的话,那么当我们遇到一个尚未完成的任务时,应该调用SynchronizationContext.Post()方法。但事实并非如此。我遗漏了什么?
辅助信息
通过我的研究,我偶然发现了这个答案。为了尝试它,我将Login()方法的实现更改为:
private static Task<Guid?> Login()
{
// return loginPromise.Task;
return Task<Guid?>.Factory.StartNew(
() =>
{
Console.WriteLine("Login()");
return null;
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}通过该修改,确实调用了Post()方法。输出:
== START ==
Before login
Post()
Before advance
PumpAll()
Login()
After login
After advance
== END ==那么,随着Jon对TaskCompletionSource的使用,他是否不需要创建ManuallyPumpedSynchronizationContext呢?
注意:我认为我看到的视频是在C# 5发布日期前后拍摄的。
发布于 2017-03-20 09:09:40
我不会讨论这段代码的目标是什么,因为我没有读过你发布的github链接上的书或全部代码。我将只处理你在当前问题中发布的代码。
我认为在您提供的代码中,不使用ManuallyPumpedSynchronizationContext (不管您在哪里运行它:控制台应用程序、单元测试、UI应用程序等等)。它的Post方法不会被调用,因为没有同步上下文切换。通常情况下,await的继续将被Post编辑为捕获的同步上下文,这在一般情况下是正确的,但是如果等待的方法完成之后,您仍然处于相同的同步上下文中--没有理由发布任何内容--您位于相同的上下文中,并且可以继续。这就是这里发生的事。当你打电话:
loginPromise.SetResult(null);当前上下文仍然是ManuallyPumpedSynchronizationContext。
但是,如果您像这样更改它:
SynchronizationContext.SetSynchronizationContext(null);
loginPromise.SetResult(null);现在,当Login()完成时,您不再处于捕获的上下文中,因此继续将确实被Post编辑到它,因此延拓将被延迟到调用PumpAll。
更新:请参阅@StephenCleary,以获得更完整的对此行为的解释(在我的回答中还有一个没有提到的因素)。
发布于 2017-03-20 08:37:35
因为您在控制台应用程序中执行代码。
控制台应用程序没有同步上下文,SynchronizationContext.Current将始终为空。
ManuallyPumpedSynchronizationContext的目的是“保存”同步上下文,其中执行测试方法,并将已完成任务的结果“泵”到保存的上下文中。
在控制台应用程序中,保存的上下文是null,所以您没有看到任何区别
https://stackoverflow.com/questions/42898828
复制相似问题