如何在后台加载图像?
我正在尝试在后台加载图像,然后更新UI。 我整天都在玩这个,我不知道我错过了什么。 我一直收到以下错误:
“调用线程无法访问此对象,因为不同的线程拥有它。”
我已经在示例后跟随示例,但我似乎无法找到答案。 我还在另一个BeginInvoke中包含了触摸UI的代码。
更新3:故事的寓意。 ImageSource对于访问不是线程安全的。
更新2:这必须是一个简单的解决方案:)。 我尝试了克隆,但这并没有带来成功,但我确实得到了一个不同的错误:“调用目标引发了exception。”
更新1:我尝试了BackgroundWorker,但我仍然得到相同的错误,但它发生在brush.ImageSource.Height上。 我是否正确地通知了用户界面? 有什么建议?
这是我的XAML:
以下是一些背后的代码:
namespace Slideshow { public class Show { public Show() { BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += new DoWorkEventHandler(bw_DoWork); bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); bw.RunWorkerAsync(); } void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BitmapSource bitmap = e.Result as BitmapSource; if (bitmap != null) { this.Dispatcher.BeginInvoke(DispatcherPriority.Normal (ThreadStart)delegate() { Image image = new Image(); image.Source = bitmap; background.Child = image; }); } } void bw_DoWork(object sender, DoWorkEventArgs e) { BitmapSource bitmapSource = CreateBrush(GetRandomFile()); e.Result = bitmapSource; } } }
我倾向于使用ThreadPool.QueueUserWorkItem
来加载图像,然后当操作完成时,我使用线程安全的Dispatcher
对象回调到UI线程。 图像源不是线程安全的,你必须使用类似JpegBitmapDecoder
东西,还有一个PngBitmapDecoder
。
例如:
public Window() { InitializeComponent(); ThreadPool.QueueUserWorkItem(LoadImage, "http://sofzh.miximages.com/c%23/simp2006_HomerArmsCrossed_f.jpg"); } public void LoadImage(object uri) { var decoder = new JpegBitmapDecoder(new Uri(uri.ToString()), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); decoder.Frames[0].Freeze(); this.Dispatcher.Invoke(DispatcherPriority.Send, new Action(SetImage), decoder.Frames[0]); } public void SetImage(ImageSource source) { this.BackgroundImage.Source = source; }
我们在项目中还有一件事。 由于ImageSource被放入UI,你必须检查它是否被冻结:
public void SetImage(ImageSource source) { ImageSource src = null; if(!source.IsFrozen) src = source.GetAsFrozen(); else src = source; this.BackgroundImage.Source = src; }
您希望在WPF应用程序中包含多个线程。 如下文所述,WPF强制您在创建UI的线程上执行所有UI工作。
看看这个: http : //pjbelfield.wordpress.com/2007/10/29/worker-threads-and-the-dispatcher-in-wpf/
并且用于密集的UI工作:
http://sofzh.miximages.com/c%23/pp 将图像(.jpg)加载到内存中的代码应该在后台完成。 将映像从内存加载到WPF控件的代码必须在普通的UI WPF线程中完成。 只应将慢速/长时间运行的任务放入后台线程中。 一旦长任务完成,它必须通知UI线程用长任务的结果更新视图。
我最喜欢的方法是使用BackgroundWorker类:
var bg = new System.ComponentModel.BackgroundWorker(); bg.DoWork += Work_Function; bg.RunWorkerCompleted += UI_Function; bg.RunWorkerAsync(); void Work_Function(object sender, DoWorkEventArgs e) { ... } void UI_Function(object sender, RunWorkerCompletedEventArgs e) { ... }
当您调用RunWorkerAsync()时,首先调用Work_Function()。 完成后,调用UI_Function()。 工作函数可以在DoWorkEventArgs.Result属性中放置一个变量,该属性可以从RunWorkerCompletedEventArgs.Result属性访问。
我想你可能也对这篇文章感兴趣: http : //www.codeproject.com/KB/WPF/WPF_Explorer_Tree.aspx它展示了如何在WPF中的树视图中设置延迟加载。 我认为基本概念(添加虚拟节点,通过加载下一个图像来拦截虚拟节点的显示)将适用于您的工作。
您现在得到的错误是好消息,这意味着BackgroundWorker
正在运行。 您可以尝试使用brush.Clone();
在RunWorkerCompleted
克隆它brush.Clone();
。
尝试删除Dispatcher.BeingInvoke部分。 BackgroundWorker的重点在于事件会自动封送回UI线程,因此您不必担心。