13

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

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);
}

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

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

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

4

5 回答 5

12

不,在回调完成之前,您的对象不会被 GC-ed。根据是否垃圾收集器在.NET 中的异步调用期间破坏临时未引用的对象?, "异步 API 保留对您的请求的引用(在提交异步 IO 操作的线程池中),因此在完成之前不会被垃圾收集。 "

但是,您的代码也在做它不需要做的事情:您不需要分离事件处理程序,也不需要在 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 以查看调用是否实际工作。

于 2009-08-14T19:31:21.660 回答
5

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

只要有足够的WebClient“活着”来服务请求并调用您的处理程序,主WebClient对象本身是否被垃圾收集是否重要?

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

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

于 2009-06-11T07:41:03.317 回答
1

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

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

于 2009-06-11T08:09:32.633 回答
0

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

于 2009-06-11T07:49:40.863 回答
0

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

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

于 2009-06-11T10:17:56.343 回答