Eliminação de objects e garbage collection antes do acionamento do evento

Um pedaço de código foi criado por alguém com quem eu estava conversando:

private void DownloadInformation(string id) { using (WebClient wc = new WebClient()) { wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(DownloadStringCompleted); wc.DownloadStringAsync(new Uri("http://www.fake.com/" + id)); } } 

O acima é uma versão simplificada disso:

insira a descrição da imagem aqui

(Eu tenho a permissão do autor para postar a imagem.)

O que me incomoda nesse código é que um manipulador de events é anexado, o DownloadStringAsync() é chamado e o bloco using termina, o que chama Dispose() no WebClient . Existe alguma coisa que impeça o WebClient de ser descartado using e até mesmo o lixo coletado antes da conclusão do DownloadStringAsync() e do evento DownloadStringCompleted ?

Existe um método mais novo, o DownloadStringTaskAsync() , que eu acho que deveria usar em conjunto com o 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)); } } 

No entanto, mesmo assim … Eu basicamente apostaria que os acionadores e manipuladores de events são chamados antes que o WebClient seja descartado.

Estou entendendo mal o ciclo de vida do WebClient nesse cenário ou esse é um design de código terrível?

O WebClient não implementa IDisposable, sua class base Component faz.

A class Component Disposes any eventhandlers que estão registrados com sua propriedade Events, mas o WebClient não usa essa propriedade.

Chamar Dispose no WebClient não tem efeito em nenhum estado gerenciado pelo webclient.

A manipulação real dos resources internos é feita no método privado DownloadBits e em uma class interna DownloadBitsState .

Portanto, o código que você mostra está livre de efeitos devido à liberação de resources muito cedo. Isso, no entanto, é causado por um detalhe de implementação. Aqueles podem mudar no futuro.

Devido ao framework manter o controle dos callbacks pendentes, você não precisa se preocupar com a garbage collection prematura também, como é explicado nesta resposta , gentilmente cedida por Alexei Levenkov.

O evento é usado apenas com o DownloadStringAsync , não com o DownloadStringTaskAsync .

Com o último, a tarefa é Task e, quando a tarefa é concluída, ela contém a resposta do download.

Como tal, o segundo exemplo pode ser reescrito como este:

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

Você está inteiramente certo em que o primeiro exemplo está terrivelmente quebrado. Na maioria dos casos, eu esperaria que o object cliente fosse descartado antes que a tarefa assíncrona fosse concluída, e pode até mesmo ser coletado como lixo, como você mencionou. O segundo exemplo, depois de perder o evento, não tem nenhum desses problemas, pois ele aguardará o download ser concluído antes de descartar o object cliente.