如何正确处理WebResponse实例的释放?

23

通常,人们会编写类似这样的代码来使用 WebRequest 下载一些数据。

using(WebResponse resp = request.GetResponse())  // WebRequest request...
   using(Stream str = resp.GetResponseStream())  
      ; // do something with the stream str
现在如果抛出了WebException异常,那么WebException会引用WebResponse对象,这个对象可能已经被Dispose调用了,也可能没有(取决于异常发生的位置或响应类是如何实现的)-我不知道。
我的问题是应该如何处理这种情况。是应该编写非常防御性的代码,在WebException对象中处理响应(这有点奇怪,因为WebException不是IDisposable),还是忽略这一点,可能访问已被处理或永远不释放IDisposable对象?
MSDN文档对WebException.Response的示例是完全不够的。

在问题标题中提及“WebException.Response”会很有用。 - Deantwo
6个回答

16

我已经用Reflector快速查看了一下,现在可以说:

  • WebResponse是一个抽象类,它将其关闭/处置行为委托给其派生类。
  • HttpWebResponse是您几乎肯定在此处使用的派生类,在其close/dispose方法中,只关心处置实际响应流。类的其余状态可以交给GC的温柔怜悯。

由此可以得出结论,只要:

  • 当您在try块中从WebResponse读取响应流时,请将其包含在using块中。
  • 如果您在catch块中从WebException读取响应流,请也将其包含在using块中。
  • 不需要担心处置WebException本身。

3
using (var x = GetObject()) {
     statements;
}

(几乎)等同于

var x = GetObject();
try {
    statements;
}
finally {
     ((IDisposable)x).Dispose();
}

因此您的对象将始终被处理。

这意味着在您的情况下

try {
    using (WebResponse resp = request.GetResponse()) {
        something;
    }
}
catch (WebException ex) {
    DoSomething(ex.Response);
}

ex.Response将与您本地的resp对象相同,当您到达catch处理程序时,该对象将被释放。这意味着DoSomething正在使用一个已释放的对象,并且很可能会因ObjectDisposedException而失败。


如果在GetResponse中抛出异常,则using语句在这里不适用,因为异常是在try-finally块之前抛出的。我不认为在catch块中响应已被释放。 - ventiseis

3

HttpWebRequest在抛出WebException之前会将底层网络流制作成内存流,因此与WebException.Response返回的WebResponse没有未托管的资源相关。

这使得在WebException.Response上调用Dispose()变得不必要。实际上,试图处理WebException.Response可能会导致头痛和问题,因为您的代码可能有调用者试图读取与其相关联属性的情况。

然而,最好的做法是,您应该处理并释放任何您拥有的IDisposable对象。如果您决定这样做,请确保您的代码不需要读取WebException.Response属性和/或其流。最好的方式是您处理异常并抛出新类型的异常,以便您尽可能地避免泄漏WebException到调用者。

同时考虑转换到HttpClient,它替代了HttpWebRequest

免责声明:无任何明示或暗示的保证。


2

我非常确定,当你使用using语句时,无论你如何退出using块(无论是通过异常、返回还是简单地进入函数),对象都会被处理掉。

如果你让WebException中的对象离开using块,我怀疑你会发现该对象已经被处理掉了。

请记住,处理对象并不一定防止以后访问它。尝试以后调用它的方法可能会导致自身异常或非常奇怪的行为(因此我不建议这样做)。但即使你处理了它,仍然会留下很大一部分对象供垃圾收集器使用,因此仍然可以访问。通常,dispose的目的是清理资源句柄(例如在此情况下活动TCP连接),出于性能原因,你不能真正让它们留在那里,直到垃圾收集器找到它们。我只是提到这一点,以澄清它既被处理也被异常保留的引用。


确实。usingtry { } finally { }的语法糖,对象将始终在finally块中被处理。 - Jeremy McGee
1
GetResponse发生异常怎么办?结果将不会被分配给resp变量,因此从表达式返回,然后由using语句处理。我知道可以访问已处理的对象,但实际上并不应该这样做。如果这是API的意图,那么API设计得是否“糟糕”呢? - Marcus
不,如果GetResponse抛出异常,那么没有对象会被处理。但是你无法知道是否已经创建了一个对象,因为只有GetResponse的内部才知道这一点,所以在这种情况下,GetResponse有责任确保没有泄漏。 - erikkallen
举个例子,只是为了说明我的问题WebRequest test = WebRequest.Create("http://foo.bar"); try { using(WebResponse resp = test.GetResponse()) ; } catch(WebException e) { Console.WriteLine(e.Response == null); }这将在控制台输出“False”。显然我没有在这里处理响应对象,所以我应该吗?我知道可能没有泄漏,但这并不是重点。 - Marcus
试试这个: WebRequest test = WebRequest.Create("http://www.google.com"); WebResponse resp = test.GetResponse(); resp.Dispose(); Console.WriteLine(resp == null); 它也会输出false。我的意思是,仅仅因为某个东西不是null并不意味着它没有被处理(事实上,调用dispose并不会使我所知道的任何类型变成null)。 - fyjham

0

一个非常有趣的问题(尽管值得指出的是,当您退出使用时,WebResponse对象将已被处理)。我的直觉是,只要您不尝试对其进行任何“操作”,那么拥有对此处置的WebResponse对象的引用并不重要。

您可以可能仍然访问实例上的某些属性以进行日志记录(例如ResponseUri),而不会收到ObjectDisposedException,但总体上异常所持有的引用不存在,因此您可以继续使用该实例。

我很想看看其他人的意见。


0

我在EF数据库连接中遇到了类似的情况。

所以我实际上创建了一个连接列表。

在游戏结束时,我循环处理所有连接并释放它们。


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