Какой лучший способ асинхронной загрузки BitmapImage в С# с помощью WPF? Кажется, существует множество решений, но существует ли стандартный шаблон или лучшая практика?
Спасибо!
Какой лучший способ асинхронной загрузки BitmapImage в С# с помощью WPF? Кажется, существует множество решений, но существует ли стандартный шаблон или лучшая практика?
Спасибо!
Я просто изучал это и должен был бросить свои два цента, хотя через несколько лет после первоначального поста (на всякий случай кто-то еще ищет то же самое, что и я).
У меня есть элемент Изображение, которому необходимо загрузить изображение в фоновом режиме с помощью Поток, а затем отобразится.
Проблема, с которой я столкнулся, заключается в том, что источник BitmapSource, it Stream и элемент Изображение должны были быть на одном и том же нить.
В этом случае использование привязки и установка IsAsynch = true вызовут исключение перекрестного потока.
BackgroundWorker отлично подходит для WinForms, и вы можете использовать его в WPF, но я предпочитаю избегать использования сборок WinForm в WPF (вздутие проекта не рекомендуется, и это также хорошее правило). В этом случае это также должно вызвать недопустимое исключение перекрестной ссылки, но я не тестировал его.
Оказывается, что одна строка кода сделает любую из этих работ:
//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};
//Create a seperate thread to load the image
ThreadStart thread = delegate
{
//Load the image in a seperate thread
BitmapImage bmpImage = new BitmapImage();
MemoryStream ms = new MemoryStream();
//A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(@"C:\ImageFileName.jpg", FileMode.Open);
MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);
bmpImage.BeginInit();
bmpImage.StreamSource = ms;
bmpImage.EndInit();
//**THIS LINE locks the BitmapImage so that it can be transported across threads!!
bmpImage.Freeze();
//Call the UI thread using the Dispatcher to update the Image control
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
img.Source = bmpImage;
img.Unloaded += delegate
{
ms.Close();
ms.Dispose();
};
grdImageContainer.Children.Add(img);
}));
};
//Start previously mentioned thread...
new Thread(thread).Start();
Предполагая, что вы используете привязку данных, устанавливая Binding.IsAsync свойство True, похоже, является стандартным способом достижения этого. Если вы загружаете растровое изображение в файле с кодом, используя фоновый поток + Объект Dispatcher является распространенным способом обновления асинхронного интерфейса пользователя
BitmapCacheOption.OnLoad
var bmp = await System.Threading.Tasks.Task.Run(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(path);
img.EndInit();
ImageBrush brush = new ImageBrush(img);
}
Чтобы подробно остановиться на ответе aku, вот небольшой пример того, где установить IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
Что бы вы сделали в XAML.
Использование или расширение System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Лично я считаю, что это самый простой способ выполнения асинхронных операций в клиентских приложениях. (Я использовал это в WinForms, но не в WPF. Я предполагаю, что это будет работать и в WPF.)
Я обычно расширяю Backgroundworker, но вам не нужно.
public class ResizeFolderBackgroundWorker : BackgroundWorker
{
public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
{
this.sourceFolder = sourceFolder;
this.destinationFolder = destinationFolder;
this.resizeTo = resizeTo;
this.WorkerReportsProgress = true;
this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
}
void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
FileInfo[] files = dirInfo.GetFiles("*.jpg");
foreach (FileInfo fileInfo in files)
{
/* iterate over each file and resizing it */
}
}
}
Вот как вы могли бы использовать его в своей форме:
//handle a button click to start lengthy operation
private void resizeImageButtonClick(object sender, EventArgs e)
{
string sourceFolder = getSourceFolderSomehow();
resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);
progressBar1.Value = 0;
progressBar1.Visible = true;
resizer.RunWorkerAsync();
}
void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
//signal to user that operation has completed
}
void genericProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
//I just update a progress bar
}
Это позволит вам создать BitmapImage в потоке пользовательского интерфейса, используя HttpClient для загрузки async:
private async Task<BitmapImage> LoadImage(string url)
{
HttpClient client = new HttpClient();
try
{
BitmapImage img = new BitmapImage();
img.CacheOption = BitmapCacheOption.OnLoad;
img.BeginInit();
img.StreamSource = await client.GetStreamAsync(url);
img.EndInit();
return img;
}
catch (HttpRequestException)
{
// the download failed, log error
return null;
}
}