.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);
}

Теперь я беспокоюсь, что ошибка с жестким воспроизведением может быть вызвана тем, что экземпляр WebClient является сборкой мусора перед вызовом DownloadDataCompleted: после выхода из моего метода DownloadData() нет очевидных ссылок на WebClient, чтобы это могло случиться.

Итак, мой вопрос: может ли это реально произойти? Я не могу воспроизвести проблему, поэтому могут произойти некоторые внутренние вещи, которые не позволяют объекту WebClient быть собранным в мусор (например, объект может регистрироваться в глобальном объекте где-то в ожидании ответа).

Код работает на .NET 2.0, если это имеет значение.

Ответ 1

Нет, ваш объект не будет GC-ed до завершения обратного вызова. Согласно Разве сборщик мусора уничтожает временно неопубликованные объекты во время асинхронных вызовов в .NET?, " асинхронный API сохраняет ссылку на ваш запрос (в пуле потоков где выполняются операции асинхронного ввода-вывода), и поэтому он не будет собирать мусор, пока он не завершится."

Но ваш код также делает то, что ему не нужно: вам не нужно отсоединять обработчик событий и не нужно вызывать Dispose на веб-клиенте. (Dispose() фактически не реализован WebClient - вы можете увидеть это в исходном источнике .NET Framework в http://referencesource.microsoft.com/netframework.aspx).

Поэтому вам действительно не нужно ссылаться на экземпляр 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);
}

В любом случае, вы, вероятно, захотите посмотреть в другом месте на источник вашей ошибки. Одно место, которое я бы посмотрел, - это результаты HTTP-вызовов - у вас может быть нехватка памяти, может быть запущена ошибка сервера и т.д. Вы можете посмотреть на e.Error, чтобы увидеть, действительно ли звонки работают.

Ответ 2

Я точно не знаю, может ли WebClient быть собранным или нет, если выполняется асинхронная операция, потому что могут быть внутренние ссылки, но вопрос большой: имеет ли значение?

До тех пор, пока достаточно WebClient останется "живым" для обслуживания запроса и вызовите обработчик, имеет ли значение, действительно ли главный объект WebClient собирает мусор?

Документация WebClient не упоминает о необходимости удерживать ссылку (в отличие от System.Threading.Timer docs, например) поэтому я считаю разумным предположить, что это нормально.

В этом конкретном случае у вашего делегата есть ссылка на WebClient, поэтому, если ссылается на сам делегат, WebClient не может быть. Моя образованная догадка заключается в том, что какая-то часть системы где-то нуждается в обратном вызове, чтобы знать, что делать, когда прибывает сетевой трафик, и что обратный вызов в конечном итоге (косвенно) приведет к вашему делегату, так что вы в порядке.

Ответ 3

Вы можете попробовать отладить приложение с помощью инструментов отладки для Windows - он позволяет вам видеть, что именно хранит ссылки на определенный объект (с соответствующим подключаемым модулем -в). Очень полезно для таких случаев.

Я не знаю ответа на ваш вопрос. Одна из возможностей заключается в том, что в течение всего срока действия WebClient делает себя одним из "корневых" объектов, которые никогда не собираются с мусором (приложение .NET обычно имеет от 5 до 10 таких объектов, которые являются корнями нескольких используемых опорных деревьев по заявке). Это чистая спекуляция.

Ответ 4

Размешение монеты... если вы где-то сохранили ссылку на WebClient, просто чтобы увидеть, не имеет ли это значение... это вообще устраняет проблему? Может быть, проще проверить это так и быть уверенным, чем угадать, что кажется логичным.

Ответ 5

При создании анонимного метода со ссылкой на переменную области видимости (webClient в вашем случае) и создайте ее собственную переменную со ссылкой на этот объект. Так как jon угадывает, что ваш делегат будет ссылаться на webClient и до того, как делегат отменит его сам, webClient не может быть собранным мусором.

Однако я обычно предлагаю не использовать ссылку webClient в методе делегата, а передавать отправителя во внутреннюю переменную. использование переменных извне анонимным методам может привести к некоторым очень странным ошибкам.