【问题标题】:Properly disposing a WebRequest and StreamReader正确处理 WebRequest 和 StreamReader
【发布时间】:2012-12-11 00:30:44
【问题描述】:

在从客户端方法 getRecords 调用 ReadToEnd 方法期间,我遇到了一个对象处理异常,该方法使用 StreamReader 与 Web 服务器通信。

第一次调用 getRecords 成功,只有在后续调用期间才会发生异常,因此我没有正确关闭和处理 StreamReader 和关联的 WebRequest。

我知道我可以将这两个对象包装在 using 语句中,但这只是扩展为 try/catch/finally 语句。从下面的代码中可以看出,我正在清理 finally 子句。

因此,我要么没有做 using 语句所做的事情,要么我的 finally 语句中可能缺少其他一些东西。如果在一切皆有可能,因为我喜欢我的代码明确。

这里是代码和相关的异常:

    public int getRecords(string[] args, string[] vals)
    {
        List<string> urlList = BuildUrlRequestStrings(args, vals); 

        WebRequest request = null;
        WebResponse wresponse = null;
        StreamReader sr = null;           

        foreach (string url in urlList)
        {   
            request = WebRequest.Create(url);

            request.Method = "GET";
            request.ContentType = "application/json";
            //request.Timeout = -1;
            request.Timeout = 300000;
            request.Credentials = CredentialCache.DefaultCredentials;
            //request.ContentType = "application/xml";

            try
            {
                wresponse = request.GetResponse();

                /*using (StreamReader sr = new StreamReader(wresponse.GetResponseStream()))
                {
                    _recieveBuffer = sr.ReadToEnd().ToString();
                }*/
                sr = new StreamReader(wresponse.GetResponseStream());
                _recieveBuffer = sr.ReadToEnd();

                //List<T> temp = JsonConvert.DeserializeObject<List<T>>(_recieveBuffer);
                List<T> temp = JsonConvert.DeserializeObject<List<T>>(
                    _recieveBuffer,
                    new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }
                );

                _recieveData.AddRange(temp);                    
            }
            catch (WebException ex)
            {
                if (ex.Response != null)
                {
                    // can use ex.Response.Status, .StatusDescription         
                    if (ex.Response.ContentLength != 0)
                    {
                        using (var stream = ex.Response.GetResponseStream())
                        {
                            using (var reader = new StreamReader(stream))
                            {
                                Log.Info(FIDB.TAG1, "   WSBuffer.getRecords: WEBSERVER MESSAGE: " + reader.ReadToEnd());
                            }
                        }
                    }
                }

                return -1;
            }
            finally
            {
                if (sr != null)
                {
                    sr.Close();
                    sr.Dispose();
                }

                if (wresponse != null)
                {
                    wresponse.Close();
                    wresponse.Dispose();
                }
            }
        }

        return _recieveData.Count;
    }

07-02 11:32:15.076: I/>>(2775): StorageRelayService.RequestQueueThread:异常: System.ObjectDisposedException:对象被使用后 处置。 07-02 11:32:15.076: I/>>(2775): 在 System.Net.WebConnection.BeginRead(System.Net.HttpWebRequest 请求, System.Byte[] 缓冲区,Int32 偏移量,Int32 大小,System.AsyncCallback cb,System.Object 状态)[0x00000] in :0 07-02 11:32:15.076: I/>>(2775): 在 System.Net.WebConnectionStream.BeginRead (System.Byte[] 缓冲区,Int32 偏移量、Int32 大小、System.AsyncCallback cb、System.Object 状态) [0x00000] in :0 07-02 11:32:15.076: I/

(2775): 在 System.Net.WebConnectionStream.Read (System.Byte[] 缓冲区,Int32 偏移量,Int32 大小) [0x00000] in :0 07-02 11:32:15.076: I/>>(2775): 在 System.IO.StreamReader.ReadBuffer () [0x00000] in :0 07-02 11:32:15.076: I/>>(2775): 在 System.IO.StreamReader.Read (System.Char[] 缓冲区,Int32 索引,Int32 计数)[0x00000] in :0 07-02 11:32:15.076: I/>>(2775): 在 FieldInspection.Shared.Buffer.WSBuffer1[FieldInspection.Shared.Model.AggregateRoot.Parcel].getRecords (System.String[] args, System.String[] vals) [0x00000] in <filename unknown>:0 07-02 11:32:15.076: I/<<< FI >>>(2775): at FieldInspection.Shared.Repository.REST.RepositoryREST1[FieldInspection.Shared.Model.AggregateRoot.Parcel].Read (IConditions 条件) [0x00000] in :0 07-02 11:32:15.076: I/>>(2775): 在 FieldInspection.Shared.Model.DataAccess.ParcelRepositoryREST.parcelByIdList (System.Collections.Generic.List1 parcelIdList, Boolean bCurrent, Boolean bHistorical) [0x00000] in <filename unknown>:0 07-02 11:32:15.076: I/<<< FI >>>(2775): at FieldInspection.Droid.StorageRelayService.ProcessRequestGetParcelCache (FieldInspection.Shared.Database.IPC.Request request) [0x00000] in <filename unknown>:0 07-02 11:32:15.076: I/<<< FI >>>(2775): at FieldInspection.Droid.StorageRelayService.ProcessRequestFromForegroundActivity (System.Collections.Generic.List1 reqList) [0x00000] in :0 07-02 11:32:15.076: I/>>(2775): at FieldInspection.Droid.StorageRelayService.RequestQueueThread() [0x00000] 在 :0

【问题讨论】:

  • 您可以使用以下内容清理您的 using 语句:stackoverflow.com/questions/1329739/…
  • 您是否有特定原因不想只使用using 声明?
  • @Jeff Hubbard 我不喜欢在扩展 using 语句时何时何地调用 finally 子句和 dispose 语句的“未知”因素,而 foreach 循环就是一个完美的例子。如前所述,我不应该在循环存在之前调用 dispose,b/c 对象将再次实例化,因此设置为 null (如建议的那样)。如果您在下面的答案中看到我的评论,我会问“编译器是否知道在循环退出之前不调用 dispose”。这些是我想避免的不确定性。
  • @SamusArin - 如果usingforeach 内,则每次迭代后都会对对象进行垃圾回收,就像您以StreamReader sr = null; 启动foreach 一样。如果using 包裹了foreach,那么您将无法从其中分配给它,因此它不需要反复关闭。这是一个完美的例子,说明为什么使用 using 是一件好事 - 您确切地知道该流的有效范围,并且您永远不会让它处于中间状态。
  • @Bobson 好吧,我会试试 using 语句,现在我知道它是如何工作的了。贤者提示,谢谢。

标签: c# .net web-services httpwebrequest


【解决方案1】:

使用using,一旦失去范围,它将关闭您的连接。

【讨论】:

  • -1: 设置为null 没有效果,有时甚至会产生负面影响。
  • 啊,我记得在同一个应用程序中连接到(本地)sqlite 数据库时遇到了类似的情况。我必须按照非常具体的顺序进行清理。这是很好的信息,谢谢。因此,如果在 foreach 中使用了“使用”语句,编译器是否知道在循环退出之前不调用 dispose(我最终会反编译以进行调查)?
  • 设置为 null 与调用 Dispose 结合使用,这应该允许在 foreach 中重用对象。坚持使用using 并将Exception 推送到上游当然是首选,或者坚持调用Close 而不是Dispose
  • 我必须将对象设置为 null 以便我的 sqlite 连接清理并且不让文件指针保持打开状态: if (_rdr != null) { _rdr.Close(); _rdr = null; } if (_cmd != null) { _cmd.Dispose(); _cmd = null; } if (_con != null) { _con.Close(); _con = null; }
  • @SamusArin 为澄清起见,如果您致电Dispose,请设置为null;否则调用 Close 就足够了,而无需像您所说的那样设置为 null
【解决方案2】:

如果你的第一次运行没问题,但你在下一次运行时遇到问题,那么很可能是因为垃圾收集器没有清理关闭的对象。在finally 块的末尾执行以下操作。

GC.Collect();
GC.WaitForPendingFinalizers();

【讨论】:

  • 我会试一试,我有兴趣看看是否会缓解这种情况(我现在开始工作,但我仍然想验证这一点)。总的来说,这是很好的信息,谢谢。
  • @SamusArin 我和你有同样的问题。关闭、处理和将所有内容设置为 null 并没有帮助。只是手动 GC 调用解决了它。欢迎你:)
  • 恕我直言,这是一种“大锤”方法,应谨慎使用。在长时间运行的任务的一个阶段之后,您要确保在继续之前处于干净状态。并且便于调试:如果这有帮助,那么在某处缺少usingdispose。试着让你的代码在没有这个的情况下工作,然后在你不介意长时间停顿的时候将其用作保险单。如果您发现自己经常这样做,则表明存在更深层次的问题。甚至有时间添加一个子进程,您可以在需要时终止(尽管这可能会使您的代码复杂化)。
  • ... 另一方面,我也遇到了这样一种情况,这是唯一的出路:在 32 位进程中将大量 DirectX/XNA 代码与 .NET 代码混合在一起.可怕的碎片,直到用这样的逻辑定期强制清理它。
  • 注意:在 Samus 的情况下,这不会有帮助。他没有在sr.Dispose(); 之后执行sr = null;,因此他的代码仍然保留对该已处理实例的引用。通常一个变量会在出现问题之前就超出范围,因此该变量会自行消失,但在 Dispose 之后始终设置为 null 是最安全的。 (推测在 Samus 的情况下,错误时刻的 WebException 使 sr 在第二次迭代中无法再次设置,因此使用了第一次迭代中的 Disposed sr,这是不允许的。)
【解决方案3】:

我知道您已经接受了答案,但另一种解决方案是将变量声明放入 foreach 循环中。

foreach (string url in urlList)
{ 
    WebRequest request = null;
    WebResponse wresponse = null;
    StreamReader sr = null;  
    ...
}

然后每个循环都会获得它自己的实例,.Dispose() 将按您的预期工作。

【讨论】:

  • 嘿,你可能不相信我,但我确实有它们,只是注释掉了。我尝试了各种各样的东西,但这是盲目的实验。这个关于 Dispose 在这种情况下如何工作的信息非常重要,我很高兴你发布了这个!非常感谢。我也要试试这个。
  • 扩展这个答案:如果 Samus 在sr.Dispose(); 之后添加了sr = null;,那么他可以确定第一次迭代中的sr 再也不会被引用。在循环内移动声明(设置为 null)是另一种确保每次迭代都是独立的方法。
【解决方案4】:

我强烈建议您使用“使用”语句。一旦你知道你的 WebResponse 和 StreamReader 被正确处理,它就变得更容易调试了。

此外,您为循环的每次迭代创建一个 WebRequest 对象。为什么不尝试异步方法?

这篇文章可能会有所帮助:How to use HttpWebRequest (.NET) asynchronously?

【讨论】:

  • 好吧,using 隐藏了编译器最终生成的 finally 子句,所以我真的不明白这对理解 SR 和 WR 是如何处理的有什么帮助。另外,我说我不喜欢 using 语句(太晦涩和“神奇”)。循环实际上是为了方便一个超过大小限制的url字符串,所以每个请求都是一个子请求,所以顺序是可以的。无论如何,我要退出循环,因为我可以在 JSON 序列化对象中传递参数(它也会隐藏它们)。感谢您提供有关异步的提示。
  • 不客气。我明白了,您可能不喜欢 using 语句,但由于它确保您的对象在完成后被释放,您不会迷失在 try{..}catch{..}finally{...} 块中.我个人觉得它更容易使用。
  • 好吧,上面 Bobson 的回答消除了我对 using statments 行为的困惑/不确定性,并且每次出现这些问题时,人们绝大多数建议使用 using statment,我也将开始.我真的很想手动清理,直到我明白发生了什么;我想我准备好了:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多