Ответ 1
Я подошел к этой проблеме несколькими способами, в том числе с WebClient и просто с BitmapImage.
EDIT: Исходное предложение заключалось в использовании конструктора BitmapImage (Uri, RequestCachePolicy), но я понял свой проект, где я тестировал этот метод использовал только локальные файлы, а не веб. Изменение руководства для использования моей другой проверенной веб-техники.
Вы должны запустить загрузку и декодирование в фоновом потоке, потому что во время загрузки, будь то синхронно или после загрузки изображения, для декодирования изображения требуется небольшое, но значительное время. Если вы загружаете много изображений, это может привести к остановке потока пользовательского интерфейса. (Здесь есть несколько других тонкостей, таких как DelayCreation, но они не применяются к вашему вопросу.)
Есть несколько способов загрузить изображение, но я нашел для загрузки из Интернета в BackgroundWorker, вам нужно загрузить данные самостоятельно с помощью WebClient или аналогичного класса.
Обратите внимание, что BitmapImage внутренне использует WebClient, плюс он имеет много ошибок обработки и настройки учетных данных и других вещей, которые мы должны были бы выяснить для разных ситуаций. Я предоставляю этот фрагмент, но он был протестирован только в ограниченном количестве ситуаций. Если вы имеете дело с прокси-серверами, учетными данными или другими сценариями, вам придется немного помассировать.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
using (WebClient webClient = new WebClient())
{
webClient.Proxy = null; //avoids dynamic proxy discovery delay
webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
try
{
byte[] imageBytes = null;
imageBytes = webClient.DownloadData(uri);
if (imageBytes == null)
{
e.Result = null;
return;
}
MemoryStream imageStream = new MemoryStream(imageBytes);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = imageStream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
imageStream.Close();
e.Result = image;
}
catch (WebException ex)
{
//do something to report the exception
e.Result = ex;
}
}
};
worker.RunWorkerCompleted += (s, e) =>
{
BitmapImage bitmapImage = e.Result as BitmapImage;
if (bitmapImage != null)
{
myImage.Source = bitmapImage;
}
worker.Dispose();
};
worker.RunWorkerAsync(imageUri);
Я тестировал это в простом проекте, и он отлично работает. Я не на 100% о том, попадает ли он в кеш, но из того, что я могу сказать из MSDN, других вопросов на форуме и Reflectoring в PresentationCore, он должен попасть в кеш. WebClient обертывает WebRequest, который переносит HTTPWebRequest и т.д., А настройки кэша передаются по каждому слою.
Параметр BitmapImage BeginInit/EndInit гарантирует, что вы можете установить нужные вам параметры одновременно, а затем во время выполнения EndInit. Если вам нужно установить какие-либо другие свойства, вы должны использовать пустой конструктор и выписать пару BeginInit/EndInit, как указано выше, установив то, что вам нужно, прежде чем вызывать EndInit.
Я обычно устанавливаю этот параметр, который заставляет его загружать изображение в память во время EndInit:
image.CacheOption = BitmapCacheOption.OnLoad;
Это позволит избавиться от возможного использования более высокой памяти для лучшей производительности во время выполнения. Если вы это сделаете, BitmapImage будет загружаться синхронно в EndInit, если BitmapImage не требует асинхронной загрузки из URL-адреса.
Дополнительные примечания:
BitmapImage будет загружать async, если UriSource является абсолютным Uri и является схемой http или https. Вы можете определить, загружается ли она, проверяя свойство BitmapImage.IsDownloading после EndInit. Есть события DownloadCompleted, DownloadFailed и DownloadProgress, но вы должны быть слишком сложными, чтобы заставить их запускать фоновый поток. Поскольку BitmapImage предоставляет только асинхронный подход, вам нужно будет добавить цикл while с эквивалентом WPF DoEvents(), чтобы поддерживать поток до тех пор, пока загрузка не будет завершена. Этот поток показывает код DoEvents, который работает в этом фрагменте:
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = uri;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
image.EndInit();
while (image.IsDownloading)
{
DoEvents(); //Method from thread linked above
}
image.Freeze();
e.Result = image;
};
В то время как вышеупомянутый подход работает, у него есть запах кода из-за DoEvents(), и он не позволяет вам настроить прокси-сервер WebClient или другие вещи, которые могут помочь с лучшей производительностью. Первый пример выше рекомендуется для этого.