SynchronizationContext 是否不再与 ExecutionContext 一起流动(从 .NET Fr



tl;博士

在.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实际上有两个捕获方法,但其中只有一个是公开的。 内部的(mscorlib的内部(是mscorlib公开的大多数异步功能所使用的功能,它可以选择允许调用方禁止捕获SynchronizationContext作为ExecutionContext的一部分;与此相对应,Run 方法还有一个内部重载,它支持忽略存储在 ExecutionContext 中的 SynchronizationContext,实际上假装没有捕获一个(这再次是 mscorlib 中大多数功能使用的重载(。 这意味着,几乎任何核心实现位于 mscorlib 中的异步操作都不会将 SynchronizationContext 作为 ExecutionContext 的一部分进行流动,但任何核心实现驻留在其他任何地方的异步操作都将将 SynchronizationContext 作为 ExecutionContext 的一部分流动。

但是,Stephen Toub 在这里清楚地谈到了 .NET Framework,并且通读了一些有关如何在 .NET Core 中实现 ExecutionContext 的源代码,似乎这可能在 .NET Core 中发生了变化。作为内部 .NET Framework ExecutionContext 方法一部分的布尔preserveSyncCtx参数在更现代的 .NET Core 实现中找不到。

但是 ExecutionContext 的Microsoft文档对于 .NET Framework 和 .NET Core 是相同的,并且状态

类为与逻辑执行线程相关的所有信息提供单个容器。这包括安全上下文、调用上下文和同步上下文。

无论压缩堆栈流向何处,托管主体、同步、区域设置和用户上下文也会流向何处。

这似乎表明SynchronizationContext仍然应该是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(。

所以我的问题是:博客文章和Microsoft文档是否已经过时,Stephen Toub 谈到的"API 设计错误"已经在 .NET Core 中"修复"了,还是我错过了什么?

Microsoft承认ExecutionContext行为确实在.NET Core中发生了变化,并在官方文档的更新中澄清了这一点。作为更改的一部分,他们添加了以下句子:

同样在 .NET Core 中,同步上下文不会随执行上下文一起流动,而在 .NET Framework 中,在某些情况下可能会流动。

看起来在.NET Core中,至少在当前的3.1版本中,ExecutionContext不再捕获SynchronizationContext。在ExecutionContext源代码中查看它。不过,如果同步上下文在ExecutionContext.Run回调中发生了更改,则会恢复同步上下文。

我认为这是有道理的,因为 with.NET 核心,SynchronizationContext只在前端代码中真正相关。他们的目标是尽可能优化服务器端代码,因此他们删除了该部分。

相关内容

最新更新