Я подошел к этой проблеме несколькими способами, в том числе с 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 будет асинхронной скачать, если UriSource является абсолютным Uri и является HTTP или HTTPS схема. Вы можете определить, загружается ли она, проверяя свойство BitmapImage.IsDownloading после EndInit. Есть события DownloadCompleted, DownloadFailed и DownloadProgress, но вы должны быть слишком сложными, чтобы заставить их запускать фоновый поток. Поскольку BitmapImage предоставляет только асинхронный подход, вам нужно будет добавить цикл while с эквивалентом WPF DoEvents(), чтобы поддерживать поток до тех пор, пока загрузка не будет завершена. This thread показывает код 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 или другие вещи, которые может помочь с лучшей производительностью. Первый пример выше рекомендуется для этого.
Вы можете разместить свой код BackgroundWorker? – Rusty