SqlDataReader抛出NullReferenceException!这可能是什么原因,我该如何调试?

5

我在错误日志中发现了这个问题,正在努力弄清楚它是如何可能的。不是每天在 .net 基础类中都会出现 NullReferenceException 异常!

1) Exception Information
*********************************************
Exception Type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Data: System.Collections.ListDictionaryInternal
TargetSite: Void Bind(System.Data.SqlClient.TdsParserStateObject)
HelpLink: NULL
Source: System.Data

StackTrace Information
*********************************************
   at System.Data.SqlClient.SqlDataReader.Bind(TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult esult)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
   at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
   at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet)
   at MyCode.Shared.Data.DataSocket.GetTable(String SPString)
   at <rest of stack trace>

我能想到的唯一可能是有两个线程同时执行同一个方法,其中一个清除或修改了传递给Fill()的DataSet。所以我的问题实际上是:
  • 这个异常是如何引发的
  • 多线程场景会导致这个异常吗
  • 我怎么确定,例如是否有办法逐步执行System.Data方法来复制问题?
顺便说一下,我发现了另外几种情况,一个在这个线程中,另一个在某人页面的 Google缓存中。然而,它们似乎都没有帮助。
我的GetTable()方法看起来像这样:
public DataTable GetTable(string SPString)
{
    //Setup the data objects by calling helper
    prepareDataAdaptor(SPString,CommandType.Text);

    dataAdaptor.Fill(dataSet);
    dataAdaptor.SelectCommand.Connection.Close();
    DataTable dt;
    //ensure we dispose okay
    using(dataSet)
    {   

        if(dataSet.Tables.Count==0)
        {
            dataSet.Tables.Add(new DataTable("EmptyTable"));
        }

        dt=dataSet.Tables[0];
        //because we are disposing we need to remove the table from the dataset
        dataSet.Tables.Clear();

    }
    return dt;
}

private void prepareDataAdaptor(string SPString,CommandType Type)
{
    checkForConnection();
    dataSet=new DataSet();
    dbCommand.CommandText=SPString;
    dbCommand.CommandTimeout = MySettings.CommandTimeout;
    dataAdaptor.SelectCommand=dbCommand;
    dataAdaptor.SelectCommand.CommandType=Type;
    dataAdaptor.SelectCommand.Connection=dbConnection;
    dataAdaptor.SelectCommand.Parameters.Clear();
}

dataAdaptor是一个实例变量,声明为IDbDataAdapter类型,并用SqlDataAdapter进行了填充。

dataSet是一个DataSet类型的实例变量。

我的理论是,线程A运行并进入SqlDataAdapter.Fill()方法的一部分。同时,线程B也在执行并做了一些会干扰线程A的事情,比如这一行:

dataAdaptor.SelectCommand.Connection.Close();

我看到我的代码不是线程安全的,但我怎么确定这是导致上面异常的问题呢?
非常感谢任何建议!
Rory
编辑:修正了拼写错误。没有在代码中更新,因为它就是这样。
更新:我同意这个代码有几个问题需要解决,但我的主要兴趣是是否有任何方法可以验证这是导致此错误的线程问题。考虑到我的应用程序,这有点牵强,但这是我能想到的唯一的事情。在修改代码以普遍改善之前,我想确定我已经找到了异常的原因,以便我可以确定我已经解决了它。
有没有办法进入.NET代码?例如,我正在使用VS 2005 / .net 2.0,但我认为在VS 2008中,您可以查看.NET框架源代码?如果是这种情况,我可以创建一个2线程场景并逐步重现此问题吗?或者是否有一种不需要我安装VS 2008的方式?

“稍微牵强”这段代码是多线程的吗?将数据集设为局部变量,看看问题是否仍然存在。 - dotjoe
4个回答

2

您的数据集不应该是类级变量。它很可能在这两个调用之间或在填充操作期间被处理或访问了其他线程。

准备数据适配器(SPString,CommandType.Text);

数据适配器填充(数据集);


2

多线程场景是否会导致此异常?

ADO.NET类(例如SqlCommand)的实例方法通常不是线程安全的。 因此,如果您从多个线程使用这些实例,您可以预期出现您描述的问题。


1

我会在 dataAdaptor.Fill(dataSet) 周围添加一些记录代码,显示线程ID和其他信息。你可以使用 Console.Writeline,但我强烈建议使用 log4net。此外,请确保您的代码是线程安全的。每个线程应该获得自己的 DataSet 和 DataAdapter,或者使用互斥锁。


0

我猜测释放数据集会使其包含的表无效!

顺便说一下,您不需要数据集,可以直接填充数据表 - 或调用另一个数据适配器函数为您创建数据表(我认为是Get)

而且它的拼写是“adapter”,而不是“adaptor”;-)


该死的传统编码人员拼写不对!把我搞得一头雾水...那么,有什么想法确保这个多线程访问和处置实际上发生了,例如复制场景吗? - Rory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接