我一直在研究一些继承的代码,它抛出一个空的引用异常,但只是在生产负载很重的时候才偶尔。异常和堆栈跟踪在日志中可见,但从未在本地看到过。我认为我有修复,但是在没有修改代码的情况下无法复制错误,所以我希望有人可以解释它可以在没有修改的情况下发生的情况。
有问题的代码是(见注释):
public class WrappedConnection : IDisposable
{
private readonly SqlConnection sqlConnection;
private readonly WrappedConnection parentManagedSqlScope;
private static AsyncLocal<WrappedConnection> currentManagedSqlConnectionScopeAccessor = new AsyncLocal<WrappedConnection>();
public WrappedConnection()
{
parentManagedSqlScope = currentManagedSqlConnectionScopeAccessor.Value;
currentManagedSqlConnectionScopeAccessor.Value = this;
sqlConnection = new SqlConnection("blah");
}
public SqlConnection Connection => sqlConnection;
public string GetConnectionString()
{
// This is throwing a null reference exception due to Connection being null
return this.Connection.ConnectionString;
}
public void Dispose()
{
if (this.parentManagedSqlScope == null)
{
sqlConnection?.Dispose();
}
currentManagedSqlConnectionScopeAccessor.Value = parentManagedSqlScope;
}
}
我认为问题是this
被分配给静态AsyncLocal,然后被访问以调用GetConnectionString
,这导致竞争条件,此时sqlConnection
尚未分配发生。请注意,这里的SqlConnection
类型是一个自定义类型,用于创建和打开基础System.Data.SqlClient.SqlConnection
我通过交换
复制了这个问题 private static AsyncLocal<WrappedConnection> currentManagedSqlConnectionScopeAccessor = new AsyncLocal<WrappedConnection>();
带
public static WrappedConnection currentManagedSqlConnectionScopeAccessor
然后运行后台线程,在非常紧密的循环中访问静态字段,而另一个线程重复实例化WrappedConnection
的新实例。
我试图想象,然后通过测试用例复制原始代码产生空引用异常的场景。我意识到,与所有竞争条件一样,很难可靠地复制,但我真正想知道的是,我所描述的内容是否有可能发生,尽管很少。
我认为修复很简单,确保currentManagedSqlConnectionScopeAccessor.Value = this;
是构造函数做的最后一件事,以确保在完全初始化之前没有任何东西可以访问它,但是能够验证这个事先是很好的。部署到生产。
修改 所以问题就变成了:两个线程可以访问同一个AyncLocal存储,从而触发一个简单的静态字段所遇到的问题吗?如果是这样,我怎么能在测试中复制它,即给两个线程相同的synchronizationContext,因此同一个aysnclocal?
正如其中一条评论所述:此代码托管在Windows服务中。
提前致谢!