.NET:我是否需要在异步下载时保留对WebClient的引用?

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

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()方法后,没有对WebClient对象的明显引用,因此可能会发生这种情况。

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

代码在.NET 2.0上运行,如果这有任何区别。

不,在回调完成之前,您的对象不会被GC编辑。 根据垃圾收集器是否在.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); } 

无论如何,你可能想在别处寻找你的bug来源。 我看到的一个地方是HTTP调用的结果 – 你可能正在耗尽内存,可能会遇到服务器错误等。你可以查看e.Error来查看调用是否真的有效。

在异步操作正在进行时,我不确定WebClient是否通常可以被垃圾收集,因为可能存在内部引用 – 但更大的问题是:它是否重要?

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

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

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

您可以尝试使用Windows调试工具调试应用程序 – 它允许您查看保留对特定对象( 具有相应插件 )的引用的确切内容。 对这种情况非常有用。

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

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

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

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