与其他测试一起运行时,AsyncLocal测试失败

时间:2020-09-25 14:00:38

标签: c# .net asynchronous .net-core scope

我正在尝试创建一个名为AuditScope的自定义范围类,从而可以通过AuditScope.Current访问当前范围。

如果存在嵌套范围,则当前范围是嵌套最多的范围。

我希望它是线程安全的,所以我使用AsyncLocal确保当前范围属于当前异步上下文,并且与其他请求没有冲突。如果遇到任何人,这类似于TransactionScope类。

这是我的作用域类:

public sealed class AuditScope : IDisposable
{
    private static readonly AsyncLocal<Stack<AuditScope>> ScopeStack = new();

    public int ExecutedByUserId { get; }

    public AuditScope(int executedByUserId)
    {
        ExecutedByUserId = executedByUserId;

        if (ScopeStack.Value == null)
        {
            ScopeStack.Value = new Stack<AuditScope>();
        }

        ScopeStack.Value.Push(this);
    }

    public static AuditScope? Current
    {
        get
        {
            if (ScopeStack.Value == null || ScopeStack.Value.Count == 0)
            {
                return null;
            }

            return ScopeStack.Value.Peek();
        }
    }

    public void Dispose()
    {
        ScopeStack.Value?.Pop();
    }
}

我的所有测试都分别通过,但是如果我同时运行所有测试,则一个测试始终会失败:

[Test]
public async Task GivenThreadCreatesScope_AndSecondThreadCreatesScope_WhenCurrentScopeAccessedOnBothThreads_ThenCorrectScopeReturned()
{
    // Arrange
    static async Task createScopeWithLifespan(int lifespanInMilliseconds)
    {
        // This line throws the error, saying it is not null (for the 2000ms scope)
        // No scope has been created yet for this async context, so current should be null
        Assert.IsNull(AuditScope.Current);

        using (var scope = new AuditScope(1))
        {
            // Scope has been created, so current should match
            Assert.AreEqual(scope, AuditScope.Current);

            await Task.Delay(lifespanInMilliseconds);

            // Scope has not been disposed, so current should still match
            Assert.AreEqual(scope, AuditScope.Current);
        }

        // Scope has been disposed, so current should be null
        Assert.IsNull(AuditScope.Current);
    }

    // Act & Assert
    await Task.WhenAll(
        createScopeWithLifespan(1000),
        createScopeWithLifespan(2000));
}

当然,由于using语句处于不同的上下文中,这应该可行吗?为什么单独运行时不通过,但与其他测试一起运行时为什么不通过?

为完整起见,请参见下面的其他测试(我正在与其他测试一起运行),但我严重怀疑它们是否会直接影响它们:

[Test]
public void GivenNoCurrentScope_WhenCurrentScopeAccessed_ThenNull()
{
    // Act
    var result = AuditScope.Current;

    // Arrange
    Assert.Null(result);
}

[Test]
public void GivenScope_WhenScopeDisposed_ThenNull()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
    }

    // Act
    var result = AuditScope.Current;

    // Arrange
    Assert.Null(result);
}

[Test]
public void GivenScopeCreated_WhenCurrentScopeAccessed_ThenScopeReturned()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        // Act
        var result = AuditScope.Current;

        // Arrange
        Assert.NotNull(result);
        Assert.AreEqual(scope, result);
    }
}

[Test]
public void GivenNestedScopeCreated_WhenCurrentScopeAccessed_ThenNestedScopeReturned()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        using (var nestedScope = new AuditScope(2))
        {
            // Act
            var result = AuditScope.Current;

            // Arrange
            Assert.NotNull(result);
            Assert.AreEqual(nestedScope, result);
        }
    }
}

[Test]
public void GivenNestedScopeCreated_WhenNestedScopeDisposed_ThenCurrentScopeRevertsToParent()
{
    // Arrange
    using (var scope = new AuditScope(1))
    {
        using (var nestedScope = new AuditScope(2))
        {
        }

        // Act
        var result = AuditScope.Current;

        // Arrange
        Assert.NotNull(result);
        Assert.AreEqual(scope, result);
    }
}

1 个答案:

答案 0 :(得分:0)

结果证明某处必须存在参考问题,因为将Stack<AuditScope>替换为ImmutableStack<AuditScope>可以解决问题。