ASP.NET - 如何正确使用(打开/关闭)连接






4.97/5 (13投票s)
这篇小文章讨论了在 ASP.NET 应用程序中处理连接的正确方法。
引言
这篇小文章讨论了在 ASP.NET 应用程序中处理连接的正确方法。 有时,不正确关闭连接会导致以下异常。
System.InvalidOperationException: Timeout expired.
The timeout period elapsed prior to obtaining a connection from the pool.
This may have occurred because all pooled connections were in use and max pool size was reached.
背景
不久前,我有机会排查一个问题。 问题出在一个 ASP.NET 网站中。 在一个简单的网页上进行一些普通的数据录入操作时,出现了上述异常。
在看到这个异常后,我相当确定代码中连接没有被正确关闭。 因此,我要求开发人员向我展示代码并开始了代码审查。 在审查了 15 分钟后,问题就摆在眼前。 问题是 APP_Code 文件夹中的一个类。 这个类在其构造函数中打开了一个新连接,并在另一个方法中执行更新操作。 此外,在更新操作之后,连接没有被关闭(希望这个类超出范围后连接会被关闭)。 在这种情况下,打开的连接数量不断增加,大约每 15 分钟就会抛出上述异常。
注意: 类似的问题也可能发生在 Windows 应用程序中,但可能性较小,因为 Windows 应用程序只有一个活动会话,并且打开连接的类可能只有一个实例,因此只有一个打开的连接。 另一方面,在 ASP.NET 网站中,多个用户可以同时访问,并且该类也可能在每个请求甚至每次请求时创建多个实例。 因此,这会导致此问题产生灾难性的影响。
这篇小文章旨在帮助所有仍在遵循类似做法的开发人员。 在这里,我将尝试指出处理连接的推荐方法。
使用代码
连接是非常有限的资源。 应该始终非常小心地处理它们,并且不应该长时间保持打开状态。 事实上,连接应该只在需要时打开,并在使用后立即关闭。 这意味着我们尽可能晚地打开连接,并尽可能快地释放它。
确保这一点的第一种方法是使用异常处理程序。 我们在 try 块中打开一个连接,在 try 块中使用它,并在 finally 块中关闭它。 这将确保即使发生未处理的错误,连接也会在 finally 块中关闭。 如果我们不使用这种方法,而只是在执行操作后在 try 块本身中关闭连接,并且发生未处理的异常,则连接将保持打开状态,直到垃圾收集器处理掉 SqlConnection
对象。 连接是一种宝贵且有限的资源,我们永远不应该依赖垃圾收集器来关闭它。 它应该始终被确定性地关闭。
SqlConnection conn = new SqlConnection("CONNECTION_STRING_HERE");
try
{
conn.Open();
//Perform DB operation here i.e. any CRUD operation
//Conn.Close(); //This should never be here
}
catch (Exception ex)
{
//Handle exception, perhaps log it and do the needful
}
finally
{
//Connection should always be closed here so that it will close always
conn.Close();
}
第二种也是更推荐的方法是将数据访问代码包装在 "using
" 块中。 using 语句显式声明我们正在使用一个可释放的对象一段时间,即只到 using 块结束。 一旦 using 块结束,CLR
将立即通过调用其 Dispose()
方法立即释放相应的对象。 Dispose()
将在处理连接之前自动关闭连接。
using( SqlConnection conn = new SqlConnection("CONNECTION_STRING_HERE") )
{
try
{
conn.Open();
//Perform DB operation here i.e. any CRUD operation
}
catch (Exception ex)
{
//Handle exception, perhaps log it and do the needful
}
} //Connection will autmatically be closed here always
但由于某些难以解释的原因,您仍然需要在某些类的构造函数中打开连接,那么规避打开连接问题的唯一方法是实现 IDisposable
模式。 以下代码片段显示了如何正确地做到这一点。
class MyClass : IDisposable
{
//The managed resource handle
SqlConnection Conn = null;
public MyClass(string path)
{
SqlConnection conn = new SqlConnection("CONNECTION_STRING_HERE");
conn.Open();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing == true)
{
conn.Close(); // call close here to close connection
}
}
~MyClass()
{
Dispose(false);
}
}
通过实现 IDisposable
的上述类,无论用户如何使用这个类,连接都将始终被关闭。 这样就不会留下打开的连接。 有关实现 IDisposable
的更多详细信息以及上述代码中的详细信息,请参阅以下文章: 理解和实现 IDisposable 接口 - 初学者教程[^]
遵循以上所有指南将确保上述异常不会因无用的打开连接而出现。
关注点
本文包含一个非常基本的概念,大多数开发人员可能已经知道所有这些知识。 但是对于那些发现这些事情仍然非常令人困惑的开发人员来说,这篇文章可能会有所帮助。
历史
- 2012 年 8 月 18 日:第一个版本