我正在尝试创建一个名为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);
}
}
答案 0 :(得分:0)
结果证明某处必须存在参考问题,因为将Stack<AuditScope>
替换为ImmutableStack<AuditScope>
可以解决问题。