我正在尝试在后台加载图片,然后更新UI。我整天都在玩这个,我不知道我错过了什么。我一直收到以下错误:
“调用线程无法访问此对象,因为另一个线程拥有它。”
我在示例后跟随示例,但我似乎无法找到答案。我还包含了在另一个BeginInvoke中触摸UI的代码。
更新3:故事的寓意。 ImageSource对于访问不是线程安全的。
更新2:这必须是一个简单的解决方案:)。我尝试了克隆,但这并没有带来成功,但我确实得到了一个不同的错误:“调用目标引发了异常。”
更新1:我尝试了BackgroundWorker,但我仍然遇到同样的错误,但它发生在brush.ImageSource.Height上。我是否正确发信号通知UI?有什么建议吗?
这是我的XAML:
<Window x:Class="Slideshow.Show"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Canvas Background="Black" Name="canvas">
<Viewbox Name="background" Stretch="Uniform">
<Rectangle name="background" />
</Viewbox>
</Canvas>
</DockPanel>
</Window>
以下是一些代码:
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;
}
}
}
答案 0 :(得分:11)
我倾向于使用ThreadPool.QueueUserWorkItem
来加载图像,然后当操作完成时,我使用线程安全的Dispatcher
对象回调UI线程。图像源不是线程安全的,您必须使用类似JpegBitmapDecoder
的内容,还有PngBitmapDecoder
。
例如:
public Window()
{
InitializeComponent();
ThreadPool.QueueUserWorkItem(LoadImage,
"http://z.about.com/d/animatedtv/1/0/1/m/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<ImageSource>(SetImage), decoder.Frames[0]);
}
public void SetImage(ImageSource source)
{
this.BackgroundImage.Source = source;
}
答案 1 :(得分:2)
我们在项目中还有一件事。由于ImageSource被放入UI,你必须检查它是否被冻结:
public void SetImage(ImageSource source)
{
ImageSource src = null;
if(!source.IsFrozen)
src = source.GetAsFrozen();
else
src = source;
this.BackgroundImage.Source = src;
}
答案 2 :(得分:1)
您希望在WPF应用程序中合并多个线程。如下文所述,WPF强制您在创建UI的线程上执行所有UI工作。
检查一下: http://pjbelfield.wordpress.com/2007/10/29/worker-threads-and-the-dispatcher-in-wpf/
并且用于密集的UI工作:
http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/
答案 3 :(得分:0)
将图像(.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中的树视图中设置延迟加载。我认为基本概念(添加虚拟节点,通过加载下一个图像来拦截虚拟节点的显示)将适用于您的工作。
答案 4 :(得分:0)
您现在收到的错误是好消息,这意味着BackgroundWorker
正在运行。您可以尝试使用RunWorkerCompleted
在brush.Clone();
中克隆它。
尝试删除Dispatcher.BeingInvoke部分。 BackgroundWorker的重点在于,事件会自动封送回UI线程,因此您不必担心。