从.NET Framework到.NET Core,SynchronizationContext是否不再与ExecutionContext一起传递?

4

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 中被“修复”,或者我漏掉了什么?


@Dai,不是的。在该测试的“外部范围”中,无论是在.NET Framework还是.NET Core上,SynchronizationContext.Current都为null,并且我的测试使用自定义同步上下文来断言它是否已通过ExecutionContext传递。 - Erlend Graff
2个回答

3
微软已经承认在.NET Core中,ExecutionContext的行为确实发生了变化,并在其官方文档的更新中进行了澄清。作为变化的一部分,他们添加了以下句子:

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


0

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接