tl;dr
在.NET Framework中,SynchronizationContext是由ExecutionContext流动的上下文之一。在.NET Core中是否仍然如此?
长问题
在Stephen Toub 2012年的博客文章ExecutionContext vs SynchronizationContext中,他写道SynchronizationContext是ExecutionContext的一部分:
SynchronizationContext不是ExecutionContext的一部分吗?
我一直在忽略一些细节,但我不能再忽略它们了。
我忽略的主要内容是,虽然ExecutionContext能够流动所有上下文(例如SecurityContext、HostExecutionContext、CallContext等),但SynchronizationContext实际上是其中之一。我个人认为,这是API设计上的一个错误,在.NET的许多版本中都会导致一些问题。尽管如此,这是我们现在拥有并长期拥有的设计,现在更改它将是一个破坏性的变化。
博客文章继续阐述了何时SynchronizationContext作为ExecutionContext的一部分进行流动,以及何时抑制该流动:
故事变得有点混乱:ExecutionContext实际上有两个Capture方法,但只有一个是公共的。内部方法(在mscorlib内部)是大多数从mscorlib公开的异步功能使用的方法,并且它可以选择允许调用者抑制将SynchronizationContext作为ExecutionContext的一部分捕获;相应地,还有一个内部重载的Run方法,支持忽略存储在ExecutionContext中的SynchronizationContext,实际上假装没有捕获(这是mscorlib中大多数功能使用的重载)。这意味着,几乎所有核心实现驻留在mscorlib中的任何异步操作都不会作为ExecutionContext的一部分流动SynchronizationContext,但是任何核心实现驻留在其他任何地方的异步操作都会作为ExecutionContext的一部分流动SynchronizationContext。然而,Stephen Toub在此明确谈到.NET Framework,并且通过阅读.NET Core中如何实现ExecutionContext的某些源代码,似乎在.NET Core中可能已经发生了改变。在较新的.NET Core实现中,找不到作为内部.NET Framework ExecutionContext方法的一部分的布尔值preserveSyncCtx参数。
但是Microsoft ExecutionContext文档适用于.NET Framework和.NET Core,并且说明:并且ExecutionContext类为执行的逻辑线程提供了一个单一的容器,其中包括安全上下文、调用上下文和同步上下文。
这似乎表明同步上下文仍应该是ExecutionContext的一部分。无论压缩堆栈流向何处,托管主体、同步、区域设置和用户上下文也会流动。
为了尝试弄清楚是否存在差异,我编写了以下NUnit测试:
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
[TestFixture]
public class ExecutionContextFlowTests
{
private class TestSynchronizationContext : SynchronizationContext
{
/// <inheritdoc />
public override SynchronizationContext CreateCopy()
{
return new TestSynchronizationContext();
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is TestSynchronizationContext;
}
/// <inheritdoc />
public override int GetHashCode()
{
return 0;
}
}
[Test]
public async Task Test()
{
/* Arrange */
var syncCtx = new TestSynchronizationContext();
Task<ExecutionContext> t;
using (ExecutionContext.SuppressFlow())
{
t = Task.Run(() =>
{
SynchronizationContext prevCtx = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(syncCtx);
try
{
return ExecutionContext.Capture();
}
finally
{
SynchronizationContext.SetSynchronizationContext(prevCtx);
}
});
}
ExecutionContext capturedContext = await t.ConfigureAwait(false);
Assert.That(capturedContext, Is.Not.Null);
Assert.That(SynchronizationContext.Current, Is.Not.EqualTo(syncCtx));
/* Act */
var syncCtxBox = new StrongBox<SynchronizationContext>();
ExecutionContext.Run(
capturedContext,
box => ((StrongBox<SynchronizationContext>)box).Value = SynchronizationContext.Current,
syncCtxBox
);
Assert.That(syncCtxBox.Value, Is.EqualTo(syncCtx));
}
}
然后,就可以看到,使用 .NET Framework 运行 通过测试,但是在 .NET Core 上 失败(我使用的是 .NET Framework 4.2.7 和 .NET Core 3.1)。
所以我的问题是:这篇博客文章和微软文档是否已经过时,Stephen Toub 谈到的“API 设计错误”是否已经在 .NET Core 中被“修复”,或者我漏掉了什么?