C# 如何向异步测试方法添加同步上下文

C# 如何向异步测试方法添加同步上下文,c#,mstest,async-await,C#,Mstest,Async Await,我有Visual Studio 2012和一个需要同步上下文的异步测试。 但是MSTest的默认同步上下文为空。 我希望测试为在具有同步上下文的WPF或WinForms UI线程上运行。 向测试线程添加SynchronizationContext的最佳方法是什么 [TestMethod] public async Task MyTest() { Assert.IsNotNull( SynchronizationContext.Current );

我有Visual Studio 2012和一个需要同步上下文的异步测试。
但是MSTest的默认同步上下文为空。
我希望测试为在具有同步上下文的WPF或WinForms UI线程上运行。
向测试线程添加SynchronizationContext的最佳方法是什么

    [TestMethod]
    public async Task MyTest()
    {
        Assert.IsNotNull( SynchronizationContext.Current );
        await MyTestAsync();
        DoSomethingOnTheSameThread();
    }

您可以创建自定义SynchronizationContext派生类,并将其注册为当前上下文。阅读Stephen Toub关于“和”的帖子

自定义SynchronizationContext只需重写接收异步执行回调的Post方法。如何执行它们取决于您


第一次发布提供了一个同步上下文,该上下文将所有已发布的操作存储在队列中,并提供了一个阻塞循环,该循环从队列中获取操作并在单个线程中执行这些操作。

您可以在我调用的
异步上下文中使用单线程
SynchronizationContext

[TestMethod]
public void MyTest()
{
  AsyncContext.Run(async () =>
  {
    Assert.IsNotNull( SynchronizationContext.Current );
    await MyTestAsync();
    DoSomethingOnTheSameThread();
  });
}

但是,这并不能完全伪造特定的UI环境,例如,
Dispatcher.CurrentDispatcher
仍然是
null
。如果需要这种程度的伪造,应该使用原始异步CTP中的
SynchronizationContext
实现。它附带了三个可用于测试的
SynchronizationContext
实现:一个通用实现(类似于我的
AsyncContext
),一个用于WinForms,一个用于WPF。

使用Panagiotis Kanavos和Stephen Cleary提供的信息,我可以编写如下的测试方法:

    [TestMethod]
    public void MyTest()
    {
      Helper.RunInWpfSyncContext( async () =>
      {
        Assert.IsNotNull( SynchronizationContext.Current );
        await MyTestAsync();
        DoSomethingOnTheSameThread();
      });
    }
内部代码现在在WPF同步上下文中运行,并处理MSTest使用的所有异常。 助手方法来自Stephen Toub:

using System.Windows.Threading; // WPF Dispatcher from assembly 'WindowsBase'

public static void RunInWpfSyncContext( Func<Task> function )
{
  if (function == null) throw new ArgumentNullException("function");
  var prevCtx = SynchronizationContext.Current;
  try
  {
    var syncCtx = new DispatcherSynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(syncCtx);

    var task = function();
    if (task == null) throw new InvalidOperationException();

    var frame = new DispatcherFrame();
    var t2 = task.ContinueWith(x=>{frame.Continue = false;}, TaskScheduler.Default);
    Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false

    task.GetAwaiter().GetResult(); // rethrow exception when task has failed 
  }
  finally
  { 
    SynchronizationContext.SetSynchronizationContext(prevCtx);
  }
}
使用System.Windows.Threading;//来自程序集“WindowsBase”的WPF调度程序
公共静态void RunInWpfSyncContext(Func函数)
{
如果(函数==null)抛出新的ArgumentNullException(“函数”);
var prevCtx=SynchronizationContext.Current;
尝试
{
var syncCtx=new DispatcherSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
var task=function();
如果(task==null)抛出新的InvalidOperationException();
var frame=新DispatcherFrame();
var t2=task.ContinueWith(x=>{frame.Continue=false;},TaskScheduler.Default);
Dispatcher.PushFrame(frame);//执行所有任务直到frame.Continue==false
task.GetAwaiter().GetResult();//任务失败时重试异常
}
最后
{ 
SynchronizationContext.SetSynchronizationContext(prevCtx);
}
}

可以声明您自己的测试方法属性,您可以在测试运行中插入自定义代码。使用该方法,您可以使用自己的[SynchronizationContextTestMethod]替换[TestMethod]属性,该属性自动使用上下文集运行测试(仅在VS2019中测试):

公共类同步ContextTestMethodAttribute:TestMethodAttribute { 公共重写TestResult[]执行(ITestMethod testMethod) { Func function=async()=> { var declaringType=testMethod.MethodInfo.declaringType; var instance=Activator.CreateInstance(declaringType); 等待InvokeMethodsWithAttribute(实例,declaringType); wait(任务)testMethod.MethodInfo.Invoke(实例,null); 等待InvokeMethodsWithAttribute(实例,declaringType); }; var result=新的TestResult(); result.output=unittestoutput.Passed; var stopwatch=stopwatch.StartNew(); 尝试 { RunInSyncContext(函数); } 捕获(例外情况除外) { result.output=unittestoutput.Failed; result.TestFailureException=ex; } 结果。持续时间=秒表。已用时间; 返回新的[]{result}; } 私有静态异步任务InvokeMethodsWithAttribute(对象实例,类型declaringType),其中A:Attribute { if(declaringType.BaseType!=typeof(对象)) 等待InvokeMethodsWithAttribute(实例,declaringType.BaseType); var methods=declaringType.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach(方法中的var methodInfo) if(methodInfo.DeclaringType==DeclaringType&&methodInfo.GetCustomAttribute()!=null) { if(methodInfo.ReturnType==typeof(任务)) { var task=(task)methodInfo.Invoke(实例,null); 如果(任务!=null) 等待任务; } 其他的 调用(实例,null); } } 公共静态void RunInSyncContext(Func函数) { if(函数==null) 抛出新ArgumentNullException(nameof(function)); var prevContext=SynchronizationContext.Current; 尝试 { var syncContext=new DispatcherSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(syncContext); var task=function(); 如果(任务==null) 抛出新的InvalidOperationException(); var frame=新DispatcherFrame(); var t2=task.ContinueWith(x=>{frame.Continue=false;},TaskScheduler.Default); Dispatcher.PushFrame(frame);//执行所有任务直到frame.Continue==false task.GetAwaiter().GetResult();//任务失败时重试异常 } 最后 { SynchronizationContext.SetSynchronizationContext(prevContext); } } }
public class SynchronizationContextTestMethodAttribute : TestMethodAttribute
{
    public override TestResult[] Execute(ITestMethod testMethod)
    {
        Func<Task> function = async () =>
        {
            var declaringType = testMethod.MethodInfo.DeclaringType;
            var instance = Activator.CreateInstance(declaringType);
            await InvokeMethodsWithAttribute<TestInitializeAttribute>(instance, declaringType);
            await (Task)testMethod.MethodInfo.Invoke(instance, null);
            await InvokeMethodsWithAttribute<TestCleanupAttribute>(instance, declaringType);
        };
        var result = new TestResult();
        result.Outcome = UnitTestOutcome.Passed;
        var stopwatch = Stopwatch.StartNew();
        try
        {
            RunInSyncContext(function);
        }
        catch (Exception ex)
        {
            result.Outcome = UnitTestOutcome.Failed;
            result.TestFailureException = ex;
        }
        result.Duration = stopwatch.Elapsed;
        return new[] { result };
    }

    private static async Task InvokeMethodsWithAttribute<A>(object instance, Type declaringType) where A : Attribute
    {
        if (declaringType.BaseType != typeof(object))
            await InvokeMethodsWithAttribute<A>(instance, declaringType.BaseType);

        var methods = declaringType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        foreach (var methodInfo in methods)
            if (methodInfo.DeclaringType == declaringType && methodInfo.GetCustomAttribute<A>() != null)
            {
                if (methodInfo.ReturnType == typeof(Task))
                {
                    var task = (Task)methodInfo.Invoke(instance, null);
                    if (task != null)
                        await task;
                }
                else
                    methodInfo.Invoke(instance, null);
            }
    }

    public static void RunInSyncContext(Func<Task> function)
    {
        if (function == null)
            throw new ArgumentNullException(nameof(function));
        var prevContext = SynchronizationContext.Current;
        try
        {
            var syncContext = new DispatcherSynchronizationContext();
            SynchronizationContext.SetSynchronizationContext(syncContext);
            var task = function();
            if (task == null)
                throw new InvalidOperationException();

            var frame = new DispatcherFrame();
            var t2 = task.ContinueWith(x => { frame.Continue = false; }, TaskScheduler.Default);
            Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false

            task.GetAwaiter().GetResult(); // rethrow exception when task has failed
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(prevContext);
        }
    }
}