A piece of code was brought up by someone I was talking to:
private void DownloadInformation(string id)
{
using (WebClient wc = new WebClient())
{
wc.DownloadStringCompleted +=
new DownloadStringCompletedEventHandler(DownloadStringCompleted);
wc.DownloadStringAsync(new Uri("http://www.fake.com/" + id));
}
}
The above is a simplified version of this:
(I have the author's permission to post the image.)
What bothers me about that code is that an event handler is attached, DownloadStringAsync()
is called and then the using
block ends, which calls Dispose()
on WebClient
. Is there anything that will prevent WebClient
from being disposed off by using
and even garbage collected prior to DownloadStringAsync()
completing and DownloadStringCompleted
event triggering?
There's a newer method, DownloadStringTaskAsync()
, which I would think to use in conjunction with 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));
}
}
However, even then... I would basically be betting that event triggers and handler gets called before the WebClient
gets disposed off.
Am I misunderstanding the life cycle of WebClient
in this scenario or is this a terrible code design?
The WebClient doesn't implement IDisposable, its base class Component does.
The Component class Disposes any eventhandlers that are registered with its Events property but the WebClient doesn't use that property.
Calling Dispose on the WebClient doesn't have an effect on any state managed by the webclient.
The actual handling of internal resources is done in the private method DownloadBits
and an internal class DownloadBitsState
.
So the code you show is up to now free of any effects due to releasing resources too early. That is however caused by an implementation detail. Those might change in the future.
Due to the framework keeping track of pending callbacks you don't have to worry about premature garbage collection as well, as is explained in this answer, kindly provided by Alexei Levenkov.
The event is only used with DownloadStringAsync
, not with DownloadStringTaskAsync
.
With the latter, the task is Task<string>
and when the task completes it contains the response from the download.
As such the second example can be rewritten as this:
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
}
}
You're entirely right in that the first example is horribly broken. In most cases I would expect the client object to be disposed before the async task completes, and it may even be garbage collected as you're mentioning. The second example, after losing the event, has none of those problems as it will correctly wait for the download to complete before disposing the client object.