【问题标题】:SqlDataReader hangs on Dispose()SqlDataReader 在 Dispose() 上挂起
【发布时间】:2013-10-30 15:07:25
【问题描述】:

我使用以下方法对数据库执行查询并读取数据:

using(SqlConnection connection = new SqlConnection("Connection string"))
{
    connection.Open();

    using(SqlCommand command = new SqlCommand("SELECT * FROM TableName", connection))
    {
        using (SqlDataReader reader = command.ExecuteReader())
        {
              // read and process data somehow (possible source of exceptions)
        } // <- reader hangs here if exception occurs
    } 
}

在读取和处理数据时,可能会发生一些异常。问题是当DataReader 抛出异常时,Close() 调用挂起。你有什么想法为什么???以及如何以适当的方式解决这个问题?当我写try..catch..finally 块而不是using 并调用command.Cancel() 之后,问题就消失了,然后将阅读器置于finally 中。

工作版本:

    using(SqlConnection connection = new SqlConnection("Connection string"))
    {
        connection.Open();

        using(SqlCommand command = new SqlCommand("SELECT * FROM TableName", connection))
        {
            SqlDataReader reader = command.ExecuteReader();
            try
            {
                // read and process data somehow (possible source of exceptions)
            }
            catch(Exception ex)
            {
                // handle exception somehow
            }
            finally
            {
               command.Cancel(); // !!!
               reader.Dispose();
            }
        } 
    }

【问题讨论】:

  • 调试器之外是否也会发生这种情况?
  • using 块会自动关闭和处理!您不必在 using 块中关闭它。同时在 using 块中向我们展示您的代码
  • try/catch 块中的全部内容告诉异常是什么。如果您不告诉我们异常是什么,我们将无能为力。
  • @P.Brian.Mackey 这发生在调试器之外,这就是我不得不使用调试器的原因。
  • @BlackFrog 嗯,异常不对应数据库交互,任何类型的异常。我自己抛出异常。如果发生任何异常,唯一的事实是阅读器挂起。

标签: c# .net sqldatareader


【解决方案1】:

发生异常时,您会在收到所有数据之前停止处理数据。如果您在几行后中止处理,即使没有异常,您也可以重现此问题。

当命令或读取器被释放时,查询仍在服务器上运行。 ADO.NET 只是疯狂地读取所有剩余的行和结果集并将它们丢弃。这样做是因为服务器正在发送它们并且协议需要接收它们。

调用SqlCommand.Cancel 会向SQL Server 发送“注意”,从而导致查询真正中止。这和在 SSMS 中按下取消按钮是一样的。

总而言之,只要您停止处理行,尽管有更多行入站,就会出现此问题。您的解决方法(调用SqlCommand.Cancel)是正确的解决方案。

【讨论】:

  • 将 SqlCommand.Cancel 移动到 catch 块以防止挂起效果。最后可能会在从服务器端读取所有记录后调用。将代码移动到 catch 块可能会节省一些处理时间!
  • 在我的情况下 SqlCommand.Cancel() 不起作用。仍然需要太多时间来关闭 DataReader
  • @HaiderAliWajihi 最近有多个 .NET 错误。我知道它们是因为我关注 GitHub 上的 corefx 存储库。尝试升级到最新版本,尽管我认为它不包含所有修复。
  • 这太疯狂了。我在 2018 年遇到了这个问题,此时 Dispose() 的运行时间超过了两秒。如果您无论如何都要处理阅读器,为什么会有人希望它在幕后阅读所有剩余的行?
  • @flodin 协议要求读取所有行。不过,有人可能会争辩说 Dispose 应该发送一个 Cancel 命令。
【解决方案2】:

关于SqlDataReaderDispose方法,MSDN(link)有这样的说法:

释放 DbDataReader 使用的资源并调用 Close

我添加的重点。如果你再去看看Close 方法(link),它会说明:

Close方法填写输出参数的值,返回 值和 RecordsAffected,增加关闭所需的时间 用于处理大型或复杂查询的 SqlDataReader。 当返回值和受查询影响的记录数 不重要,关闭 SqlDataReader 所需的时间 可以通过调用关联的Cancel方法来减少 调用 Close 方法之前的 SqlCommand 对象。

因此,如果您需要停止遍历阅读器,最好先取消该命令,就像您的工作版本一样。

【讨论】:

    【解决方案3】:

    我不会那样格式化它。
    打开();不在 try 块中,它可以抛出异常
    执行阅读器();不在 try 块中,它可以抛出异常
    我喜欢 reader.Close - 因为这就是我在 MSDN 示例中看到的内容
    我捕捉到 SQLexception,因为它们有数字(比如超时)

    SqlConnection connection = new SqlConnection();
    SqlDataReader reader = null;
    try
    {
        connection.Open();  // you are missing this as a possible source of exceptions
        SqlCommand command = new SqlCommand("SELECT * FROM TableName", connection);
        reader = command.ExecuteReader();  // you are missing this as a possible source of exceptions
        // read and process data somehow (possible source of exceptions)
    }
    catch (SqlException ex)
    {
    }
    catch (Exception ex)
    {
        // handle exception somehow
    }
    finally
    {
        if (reader != null) reader.Close();
        connection.Close();
    }
    

    【讨论】:

    • 谢谢,我特意放了一个从msdn示例中得到的简化版代码,只是为了重现我的问题。
    • 那么问题是什么?这是我认为正确的方法。这和你认为合适的不一样。正如我指出的那样,您的代码没有在 Open 或 ExecuteReader 上捕获异常。
    • 问题是“为什么没有显式调用 command.Cancel() 阅读器会挂起”?我不认为这是正常行为。
    • 所以你的问题是你没有异常处理,你想知道为什么它挂在异常上。当您有未处理的异常时,就会发生这种情况。你如何断定它挂在 Close 上?
    • 对不起,但是当发生异常时,正在处理阅读器,并且在那一刻程序挂起!正好在关闭阅读器的那一刻。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    • 2011-03-24
    • 2011-12-09
    • 2017-01-16
    • 1970-01-01
    相关资源
    最近更新 更多