对只读字段的Asynclocal访问导致空引用异常

时间:2018-02-28 17:53:35

标签: c# multithreading async-await

我一直在研究一些继承的代码,它抛出一个空的引用异常,但只是在生产负载很重的时候才偶尔。异常和堆栈跟踪在日志中可见,但从未在本地看到过。我认为我有修复,但是在没有修改代码的情况下无法复制错误,所以我希望有人可以解释它可以在没有修改的情况下发生的情况。

有问题的代码是(见注释):

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服务中。

提前致谢!

0 个答案:

没有答案