事件触发前的对象处理和垃圾收集
一段代码由我正在与之交谈的人提出:
private void DownloadInformation(string id) { using (WebClient wc = new WebClient()) { wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(DownloadStringCompleted); wc.DownloadStringAsync(new Uri("http://www.fake.com/" + id)); } }
以上是此简化版:
(我已获得作者的许可发布图片。)
令我烦恼的是,该代码附带了一个事件处理程序,调用了DownloadStringAsync()
,然后using
结束,在WebClient
上调用了Dispose()
。 在DownloadStringAsync()
完成和DownloadStringCompleted
事件触发之前,是否有任何东西会阻止WebClient
被using
甚至垃圾收集掉?
有一个更新的方法, DownloadStringTaskAsync()
,我认为它与await
一起使用:
private async Task DownloadInformation(string id) { using (WebClient wc = new WebClient()) { wc.DownloadStringCompleted += DownloadStringCompleted; await wc.DownloadStringTaskAsync(new Uri("http://www.fake.com/" + id)); } }
然而,即便如此……我基本上会打赌在WebClient
被处理掉之前调用事件触发器和处理程序。
在这种情况下,我是否误解了WebClient
的生命周期,还是这是一个糟糕的代码设计?
WebClient没有实现IDisposable,它的基类是Component。
Component类释放使用其Events属性注册的任何事件处理程序,但WebClient不使用该属性。
在WebClient上调用Dispose对webclient管理的任何状态都没有影响。
内部资源的实际处理是在私有方法DownloadBits
和内部类DownloadBitsState
。
因此,您显示的代码现在由于过早释放资源而没有任何影响。 然而,这是由实现细节引起的。 那些可能会在未来发生变化。
由于框架跟踪待处理的回调,您不必担心过早的垃圾收集,正如本回答中所解释的那样,由Alexei Levenkov友情提供。
该事件仅用于DownloadStringAsync
,而不用于DownloadStringTaskAsync
。
对于后者,任务是Task
,当任务完成时,它包含下载的响应。
因此,第二个例子可以改写为:
private async Task DownloadInformation(string id) { using (WebClient wc = new WebClient()) { string response = await wc.DownloadStringTaskAsync(new Uri("http://www.fake.com/" + id)); // TODO: Process response } }
你完全正确,第一个例子是可怕的破坏。 在大多数情况下,我希望在异步任务完成之前处理客户端对象,甚至可能在您提及时进行垃圾收集。 丢失事件后的第二个示例没有这些问题,因为它将在处置客户端对象之前正确等待下载完成。