在 C# 中我在方法中有以下代码:

async Task<SqlDataReader> GetSourceData()
{
    await using var sourceConnection = new SqlConnection(GetSourceConnectionString());
    await sourceConnection.OpenAsync(cancellationToken);
    var commandSourceData = new SqlCommand(GetSelectStatement(), sourceConnection);
    return await commandSourceData.ExecuteReaderAsync(cancellationToken);
}

现在返回的读取器不起作用,因为底层连接已关闭。我推测这是因为sourceConnection我从GetSourceData方法块返回后变量就被释放了。

我的问题是:如果我using从实例化的行中删除语句sourceConnection,并且我没有手动处理返回的内容DataReader,那么是否sourceConnection会被处理,如果是,什么时候?

13

  • 5
    “sourceConnection 会被释放吗?如果会,什么时候释放?”当然会。但是 GC 何时释放还不确定您唯一可以确定的是,当变量超出范围时, i有资格using被回收。 -statement 使这一点具有确定性,因为您可以更好地控制何时发生这种情况。


    – 


  • 4
    ^^ 或者相反:不返回读取器,而是返回读取的数据。那么完整的生命周期就范围内。但如果您需要“流式”或延迟执行行为,则不适用。


    – 


  • 2
    如果不是内存泄漏,那么就是“连接泄漏”。这可能同样存在错误。我猜最佳做法是使用推荐的连接池。


    – 


  • 2
    好的,谢谢大家。我已经知道继续操作所需的信息了。我将确保通过using块或手动处理正确处理连接和读取器。


    – 

  • 2
    CommandBehavior.CloseConnection将参数传递给ExecuteReaderAsync方法。


    – 


最佳答案
2

我的问题是:如果我从实例化 sourceConnection 的行中删除 using 语句,并且我没有手动处理返回的 DataReader,那么 sourceConnection 是否会被处理,如果是,什么时候?

您想了解有关信息。这是在对象垃圾收集期间调用的一种方法,用于释放对象拥有的任何本机资源。但即使对象符合收集条件,也无法保证何时会收集该对象,或者是否会发生收集。

简单的移除using可能不会立即产生不利影响,但迟早可能会导致其他问题,例如连接耗尽。而且由于未处置的对象而导致的问题可能很难找到和修复。因此建议将终结器视为安全网。有它是一件好事,但不应依赖它。

更好的解决方案是返回数据而不是读取器。另一种可能性是重构代码以确保连接生命周期超过读取器的生命周期,例如,通过将方法更改为在完成时同时处置读取器和连接的类。您还应确保 SQLCommand 和任何其他实现 IDisposable 的对象都被处置。可能的例外是

4

  • 2
    will likely not have immediate detrimental effect它会非常快地完成,因为幽灵连接获取的锁会阻止其他查询。尤其是在 Web 应用程序中。执行 UPDATE 的孤立连接最终可能会阻止其他尝试从该表读取的连接。连接泄漏是可扩展性问题的最大(也是最容易解决的)原因


    – 


  • @PanagiotisKanavos 你确定吗?我以为锁是在其他层面管理的。我相当确定许多 ORM 池化连接,如果它们持有锁,这会导致各种问题。但我同意,如果频繁使用未处置的连接,很快就会导致问题。


    – 

  • 是的,它们不是在另一个级别进行管理的,在重置它们以清除所有待处理的锁之后,连接由 ADO.NET 而不是 ORM 池化。如果您使用显式事务,则锁会在事务持续期间保持。事务外锁的生命周期取决于事务隔离级别,但在 SQL Server 中,最好假设它们在连接持续期间保持。


    – 


  • @PanagiotisKanavos 谢谢你的解释!我将措辞改为“可能”,因为如果我理解正确的话,这将取决于查询的类型、查询的运行频率等。


    – 

当您从方法中返回时SqlDataReader,您将关闭连接的责任转移给调用者。您可以通过使用以下选项创建读取器来执行此操作

return await commandSourceData.ExecuteReaderAsync(
    CommandBehavior.CloseConnection, cancellationToken);

这样,当调用者关闭或处置读取器时,连接将被关闭(或更准确地返回到 ADO.NET 池)。

如果我不手动处理退回的物品DataReader,它会sourceConnection被处理吗?如果会,什么时候?

在这种情况下,当垃圾收集器调用连接的终结器时,连接将被释放。在此之前(您不知道何时会发生这种情况),连接将保持打开状态。多次执行此操作,ADO.NET 维护的连接池将被耗尽。在这种情况下,根据

连接池程序通过在将连接释放回池中时重新分配连接来满足连接请求。如果已达到最大池大小且没有可用的连接,则请求将排队。然后,池程序会尝试回收所有连接,直到达到超时(默认值为 15 秒)。如果池程序在连接超时之前无法满足请求,则会引发异常。

总的来说,我认为暴露DataReaders 可能会提高应用程序的性能(通过消除对中间内存存储的需求),但也会增加风险,因为会使代码更容易被误用。因此,请确保性能上的潜在好处能够抵消可维护性方面的成本。