【问题标题】:SynchronizationContext is not flowed when using await使用 await 时 SynchronizationContext 不流动
【发布时间】:2013-04-19 17:05:30
【问题描述】:

我们计划在我们的 MVVM 视图模型中使用 async/await,但是在对这段代码进行单元测试时遇到了一个难题。当使用 NUnit 和手写模拟进行消息传递时,我们正在丢失当前的 SynchronizationContext

最好用下面的小复制示例代码显示:

[Test] public void TestMethod()
{       
  Func<Task> asyncMethod = async () =>
    {
      var context = SynchronizationContext.Current;
      await TaskEx.Yield();
      Assert.AreEqual(context, SynchronizationContext.Current);
    };

    // Establish the new context
    var syncCtx = new SingleThreadSynchronizationContext(false);
    SynchronizationContext.SetSynchronizationContext(syncCtx);

    // Invoke the function and alert the context to when it completes
    var t = asyncMethod();
    t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

    // Pump continuations and propagate any exceptions
    syncCtx.RunOnCurrentThread();
    t.GetAwaiter().GetResult();
}

实际上大部分代码是从 Stephen Toub on his blog 的 AsyncPump 实现中窃取的。

要通过这个测试,有趣的是在调用异步方法之前输入ExecutionContext.SuppressFlow();。这可能足以解决我们的问题,但我对 ExecutionContext 了解不够,我想更深入地了解正在发生的事情。

为什么await语句生成的代码会吞下当前的SynchronizationContext?
使用单线程上下文对 async/await 代码进行单元测试还有其他明显的方法吗?

PS:我们正在使用 .Net4 和 Microsoft.CompilerServices.AsyncTargetingPack.Net4

PPS:这也发生在使用稳定的 Microsoft.Bcl.Async 而不是 ATP 的简单项目中

【问题讨论】:

  • 请升级到Microsoft.Bcl.Async
  • 我看到 ATP 已经过时了,你认为这是一个错误并在新版本中修复了吗?我现在不能轻易更改版本,也许是星期一...
  • 我不确定,但 ATP 是一个非常旧的版本。
  • 我相信在单元测试中同步上下文的处理将不同于 UI。我不确定您正在测试的内容实际上会告诉您任何有用的信息。例如在测试中,没有“消息泵”可以封送回,因此框架不一定知道如何将执行注入到原始同步上下文中。
  • 升级到 Microsoft.Bcl.Async 没有帮助。

标签: c# .net mvvm nunit async-await


【解决方案1】:

我遇到了完全相同的问题。

我发现这是因为我的自定义 SynchronizationContext 没有正确覆盖和实现 CreateCopy 方法。似乎异步代码在每个任务(或某事)之后创建了上下文的副本。确保你的也正确实现它。

【讨论】:

  • 这很有帮助,尽管我仍然不太了解整个问题。我们也不能覆盖 NUnit 上下文。真的是时候更新了...
【解决方案2】:

您在 .NET 4.0 中遇到了一个在 .NET 4.5 中已修复的错误:

SynchronizationContext.Current is null in Continuation on the main UI thread

这是同样的问题,因为await 之后的代码将被包裹在一个延续中。

【讨论】:

  • 这些问题似乎相关,但我不太相信。我们得到一个 SynchronizationContext.Current。只是一个错误的。它必须与 SynchronizationContext 如何与 ExecutionContext 一起流动。我将在我们的解决方案之外做一个独立的测试,看看它在不同环境下的结果。
  • 你得到的是什么类型的?
  • 我在调查时读到的一些不错的读物:blogs.msdn.com/b/pfxteam/archive/2012/06/15/… 我很难理解为什么要将默认的 SynchronizationContext 设置为当前的(除非测试框架将其设置在某处)。也许在 Synchronizationcontext.Setsynchronizationcontext 上设置断点将帮助您找出谁首先将默认断点设置为当前断点。
  • 我相信它是由流动的 ExecutionContext 设置的。但我会验证这一点。
  • 嗯,这似乎与您的问题非常相似。上下文是从 ExecutionContext.Run 设置的,它只出现在 .Net 4.0 下,并在 .Net 4.5 中固定。唯一的区别是我们得到一个 ThreadPool Context 而不是 null...
猜你喜欢
  • 2023-03-20
  • 1970-01-01
  • 2014-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-19
相关资源
最近更新 更多