Object disposal and garbage collection prior to ev

2019-03-03 04:04发布

问题:

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?

回答1:

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.



回答2:

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.