【问题标题】:.NET: Do I need to keep a reference to WebClient while downloading asynchronously?.NET:异步下载时是否需要保留对 WebClient 的引用?
【发布时间】:2010-11-02 01:29:02
【问题描述】:

我在一段生产代码中使用如下方法:

private void DownloadData(Uri uri)
{
    WebClient webClient = new WebClient();
    DownloadDataCompletedEventHandler eh = null;
    eh = delegate(object sender, DownloadDataCompletedEventArgs e)
        {
            webClient.DownloadDataCompleted -= eh;
            ((IDisposable) webClient).Dispose();
            OnDataDownloaded();
        };
    webClient.DownloadDataCompleted += eh;
    webClient.DownloadDataAsync(uri);
}

我现在担心在调用DownloadDataCompleted 事件之前被垃圾收集的WebClient 实例可能会导致难以重现的错误:退出我的DownloadData() 方法后,没有明显引用@ 987654325@ 对象,所以这可能会发生。

所以我的问题是:这真的会发生吗?我无法重现该问题,因此可能发生了一些内部问题,阻止了 WebClient 对象被垃圾收集(例如,在等待响应时,该对象可能会在某处向全局对象注册自身)。

如果这有什么不同的话,代码在 .NET 2.0 上运行。

【问题讨论】:

    标签: c# .net asynchronous garbage-collection webclient


    【解决方案1】:

    我不确定 WebClient 在异步操作进行时是否可以正常进行垃圾回收,因为可能存在内部引用 - 但更大的问题是:这有关系吗?

    只要有足够的WebClient 保持“活动”状态以服务请求并调用您的处理程序,主WebClient 对象本身是否被垃圾回收是否重要?

    WebClient 文档没有提到必须保留引用的任何内容(例如,与 System.Threading.Timer docs 不同)所以我认为可以合理地假设这是可以的。

    在这种特殊情况下,您的委托引用了WebClient,因此只要引用了委托本身,WebClient 就不可能。我有根据的猜测是系统的某些部分 somewhere 需要保存回调以知道当网络流量到达时要做什么,并且该回调最终会(间接)导致您的委托,所以您好的。

    【讨论】:

    • 您当然是正确的,重要的问题是委托是否被垃圾收集。是的,应该认为 IO 完成系统需要持有对委托的(间接)引用。但这个引用可能是 WeakReference 似乎并非完全不可能。
    • 是的,并非不可能。但我希望会记录在案(因为它适用于 Timer)。
    【解决方案2】:

    硬币的另一面...如果您在某处存储了对WebClient 的引用,只是为了看看它是否有影响...这是否会使问题完全消失?以这种方式检查并确保比猜测看起来合乎逻辑的内容可能更容易。

    【讨论】:

      【解决方案3】:

      您可以尝试使用 Debugging Tools for Windows 来调试应用程序 - 它允许您查看究竟是什么保留了对特定对象的引用 (with the appropriate plug-in)。对于这种情况非常有用。

      不过,我不知道您的问题的答案。一种可能性是,在操作期间,WebClient 使自己成为“根”对象之一,这些对象永远不会被垃圾收集(.NET 应用程序通常有大约 5 到 10 个这样的对象,它们是使用的几个参考树的根由应用程序)。不过,这纯属猜测。

      【讨论】:

        【解决方案4】:

        当创建一个引用范围变量(在您的情况下为 webClient)的匿名方法并使用对该对象的引用使其成为自己的变量时。因此,正如 jon 猜测您的委托将持有对 webClient 的引用,并且在委托取消注册它自己之前,webClient 不能被垃圾收集。

        但是,我通常建议不要在您的委托方法中使用 webClient 引用,而是将 sender 强制转换为内部变量。在匿名方法的外部使用变量确实会导致一些非常奇怪的错误。

        【讨论】:

          【解决方案5】:

          不,在回调完成之前,您的对象不会被 GC 处理。根据Does the Garbage Collector destroy temporarily unreferenced objects during async calls in .NET?,“异步 API 保留对您的请求的引用(在异步 IO 操作所在的线程池中),因此在完成之前不会被垃圾收集。” /p>

          但是,您的代码也在做它不需要做的事情:您不需要分离事件处理程序,也不需要在 webclient 上调用 Dispose。 (Dispose() 实际上不是由 WebClient 实现的——您可以在 http://referencesource.microsoft.com/netframework.aspx 的 .NET Framework 参考源中看到这一点)。

          因此,您实际上不需要在回调中引用 webclient 实例。换句话说,以下代码也可以正常工作,并避免从委托内部引用外部局部变量的任何潜在问题(如上所述)。

          private void DownloadData(Uri uri)
          {
              WebClient webClient = new WebClient();
              DownloadDataCompletedEventHandler eh = null;
              eh = delegate(object sender, DownloadDataCompletedEventArgs e)
              {
                  OnDataDownloaded();
              };
              webClient.DownloadDataCompleted += eh;
              webClient.DownloadDataAsync(uri);
          }
          

          无论如何,您可能想在别处寻找错误的来源。我要看的一个地方是 HTTP 调用的结果——您可能内存不足,可能遇到服务器错误等。您可以查看 e.Error 以查看调用是否实际工作。

          【讨论】:

          • 您忘记附加事件处理程序:webClient.DownloadDataCompleted += eh;
          • 已修复。谢谢你让我知道。有趣的是,自从我写下这个答案以来的 7 年里,没有人看到这一点。 ;-)
          • 嘿,是的。昨天我有一个问题@@stackoverflow.com/questions/39609125/…,你的回答被认为是一个宝贵的资源。
          猜你喜欢
          • 1970-01-01
          • 2017-03-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-13
          • 1970-01-01
          • 2012-06-29
          相关资源
          最近更新 更多