Я подошел к этой проблеме несколькими способами, в том числе с помощью WebClient и только с BitmapImage.
РЕДАКТИРОВАНИЕ: Первоначально было предложено использовать конструктор 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, других вопросов на форуме и рефлексии в PresentationCore, он должен попадать в кеш. WebClient оборачивает WebRequest, который оборачивает HTTPWebRequest и т. Д., И параметры кэша передаются каждому слою.
Пара BitmapImage BeginInit / EndInit гарантирует, что вы можете установить нужные параметры одновременно, а затем во время EndInit он будет выполняться. Если вам нужно установить какие-либо другие свойства, вы должны использовать пустой конструктор и выписать пару BeginInit / EndInit, как описано выше, устанавливая то, что вам нужно, перед вызовом EndInit.
Обычно я также устанавливаю эту опцию, которая заставляет его загружать изображение в память во время EndInit:
image.CacheOption = BitmapCacheOption.OnLoad;
Это компенсирует возможное более высокое использование памяти для лучшей производительности во время выполнения. Если вы сделаете это, BitmapImage будет загружаться синхронно в EndInit, если только BitmapImage не требует асинхронной загрузки с URL-адреса.
Дополнительные примечания:
BitmapImage будет асинхронно загружаться, если 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 или другие вещи, которые могут помочь в повышении производительности. Первый пример выше рекомендуется поверх этого.