使用WPF在C#中异步加载BitmapImage

使用WPF在C#中异步加载BitmapImage的最佳方法是什么? 似乎存在许多解决方案,但是存在标准模式还是最佳实践?

谢谢!

我只是在调查这个并且不得不投入我的两分钱,虽然在原始post后几年(以防万一其他人来寻找我正在研究的同样的事情)。

我有一个Image控件,需要使用Stream在后台加载它的图像,然后显示。

我一直遇到的问题是BitmapSource ,它的Stream源和Image控件都必须在同一个线程上。

在这种情况下,使用Binding并将其设置为IsAsynch = true将引发跨线程exception。

BackgroundWorker非常适合WinForms,你可以在WPF中使用它,但我更喜欢避免在WPF中使用WinForm程序集(不建议对项目进行膨胀,这也是一个很好的经验法则)。 在这种情况下,这应该抛出一个无效的交叉引用exception,但我没有测试它。

事实certificate,一行代码将使这些工作中的任何一个:

//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对象是更新UI异步的常用方法

  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中所做的。

这将允许您通过使用HttpClient进行异步下载在UI线程上创建BitmapImage:

 private async Task 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; } } 

使用或扩展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 }