.NET: Нужно ли сохранять ссылку на WebClient при асинхронной загрузке? - PullRequest
12 голосов
/ 11 июня 2009

Я использую следующий метод в части производственного кода:

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() явных ссылок на * нет 1007 * объект, чтобы это могло произойти правдоподобно.

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

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

Ответы [ 5 ]

12 голосов
/ 14 августа 2009

Нет, ваш объект не будет 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, чтобы увидеть, действительно ли вызовы работают.

5 голосов
/ 11 июня 2009

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

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

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

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

1 голос
/ 11 июня 2009

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

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

0 голосов
/ 11 июня 2009

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

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

0 голосов
/ 11 июня 2009

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

...