【发布时间】:2022-02-05 06:35:39
【问题描述】:
我正在尝试创建一个名为 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);
}
}
【问题讨论】:
标签: c# .net asynchronous .net-core scope