SqlDataReader在Dispose()时挂起

7
我使用以下方法来执行数据库查询和读取数据:

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

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 并在 finally 中释放读取器之前调用 command.Cancel() 时,问题已经解决。

工作版本:

    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();
            }
        } 
    }

1
整个代码块中的 try/catch 告诉我们异常是什么。如果您不告诉我们异常是什么,我们就无法提供帮助。 - Black Frog
@P.Brian.Mackey 这是在调试器外发生的,所以我不得不使用调试器。 - Qué Padre
@BlackFrog 嗯,异常并不对应于数据库交互,而是任何类型的异常。我自己抛出异常。唯一的事实是,如果发生任何异常 - 读取器会挂起。 - Qué Padre
@QuéPadre:试一下并让我们知道它对你有何作用。 - John Saunders
@QuéPadre:太好了!如果其中一个答案对您有帮助,那么您应该接受它(勾选复选框)。 - John Saunders
显示剩余14条评论
3个回答

10

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

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

调用 SqlCommand.Cancel 发送一个“attention”到 SQL Server,导致查询真正中止。这与在 SSMS 中按下取消按钮相同。

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


将SqlCommand.Cancel移动到catch块中以防止挂起效应。最终可能在从服务器端读取所有记录后调用。将代码移动到catch块中可以节省一些处理时间! - vijayst
在我的情况下,SqlCommand.Cancel() 不起作用。仍然需要太长时间才能关闭 DataReader。 - Haider Ali Wajihi
1
@HaiderAliWajihi 最近有多个关于.NET的漏洞。我知道这些问题是因为我在GitHub上关注了corefx存储库。尝试升级到最新版本,虽然我认为它并不包含所有修复程序。 - usr
这太疯狂了。我在2018年遇到了这个问题,Dispose()方法需要超过两秒才能运行。如果你要处理读取器,为什么还要让它在幕后读取所有剩余的行呢? - flodin
@flodin 协议要求读取所有行。虽然可以认为Dispose应该发送Cancel命令。 - usr

3
关于 SqlDataReaderDispose 方法,MSDN (链接) 有如下说明:

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

我强调了一下。然后如果你再去看看 Close 方法(链接),它说明:

Close 方法填充输出参数、返回值和 RecordsAffected 的值,并增加关闭 SqlDataReader 所需的时间,特别是对于处理大型或复杂查询的 SqlDataReader。当查询的返回值和受影响的记录数没有太大意义时,可以通过在 Close 方法之前调用相关联的 SqlCommand 对象的 Cancel 方法来减少关闭 SqlDataReader 所需的时间。

因此,如果您需要停止迭代读取器,则最好像您的工作版本所做的那样先取消命令。

-2

我不会以那种方式进行格式化。
Open(); 不在 try 块中,它可能会抛出异常
ExecuteReader(); 不在 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 示例中获取的,只是为了重现我的问题。 - Qué Padre
那么问题是什么?这是我认为的正确方式。这与你认为的正确方式不同。正如我指出的,你的代码没有在Open或ExecuteReader上捕获异常。 - paparazzo
1
问题是:“为什么读取器在没有显式调用command.Cancel()的情况下挂起了?”我认为这不是正常行为。 - Qué Padre
所以你的问题是你没有异常处理,你想知道为什么它会在异常上挂起。这就是当你有一个未处理的异常时会发生的事情。你怎么得出结论说它卡在了Close上? - paparazzo
但是你同时使用了Close和Dispose。你如何确定}是什么?此外,你在哪里关心这个问题?为什么你不在数据库上进行异常处理呢? - paparazzo
显示剩余4条评论

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