C# 如何在后台加载图像?
我正在尝试在后台加载图像,然后更新UI。我已经玩了一整天,我不知道我错过了什么。我不断得到以下错误: “调用线程无法访问此对象,因为其他线程拥有它。” 我在下面一个又一个例子中搜寻,但似乎找不到答案。我还将触摸UI的代码包装到另一个BeginInvoke中 更新3:故事的寓意。ImageSource对于访问来说不是线程安全的 更新2:这必须是一个简单的解决方案:)。我尝试了克隆,但没有成功,但我得到了一个不同的错误:“异常已被调用的目标抛出。” 更新1:我尝试了BackgroundWorker,但仍然得到相同的错误,但它发生在brush.ImageSource.Height上。我是否正确地向用户界面发送信号?有什么建议吗 这是我的XAML:C# 如何在后台加载图像?,c#,wpf,image,delegates,C#,Wpf,Image,Delegates,我正在尝试在后台加载图像,然后更新UI。我已经玩了一整天,我不知道我错过了什么。我不断得到以下错误: “调用线程无法访问此对象,因为其他线程拥有它。” 我在下面一个又一个例子中搜寻,但似乎找不到答案。我还将触摸UI的代码包装到另一个BeginInvoke中 更新3:故事的寓意。ImageSource对于访问来说不是线程安全的 更新2:这必须是一个简单的解决方案:)。我尝试了克隆,但没有成功,但我得到了一个不同的错误:“异常已被调用的目标抛出。” 更新1:我尝试了BackgroundWorker,
<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>
您希望在WPF应用程序中合并多个线程。如本文所述,WPF强制您在创建UI的线程上完成所有UI工作 看看这个: 对于密集的UI工作:
将图像(.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_函数()。完成后,将调用UI_函数()。work函数可以将一个变量放置在DoWorkEventArgs.Result属性中,该属性可从RunWorkerCompletedEventArgs.Result属性访问
我想您可能也对本文感兴趣:它展示了如何在WPF的树视图中设置延迟加载。我认为基本概念(添加一个虚拟节点,通过加载下一个图像截取虚拟节点的显示)将适用于您的工作。您现在得到的错误是好消息,这意味着
BackgroundWorker
正在工作。您可以尝试使用brush.Clone()在RunWorkerCompleted
中克隆它代码>
尝试删除Dispatcher.BeingInvoke部分。BackgroundWorker的全部要点是,事件会自动封送回UI线程,这样您就不必担心了。我倾向于使用加载映像,然后在操作完成后,使用线程安全对象调用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;
}
公共窗口()
{
初始化组件();
ThreadPool.QueueUserWorkItem(LoadImage,
"http://z.about.com/d/animatedtv/1/0/1/m/simp2006_HomerArmsCrossed_f.jpg");
}
公共void加载映像(对象uri)
{
var decoder=新的JpegBitmapDecoder(新Uri(Uri.ToString())、BitmapCreateOptions.PreservePixelFormat、BitmapCacheOption.OnLoad);
解码器。帧[0]。冻结();
this.Dispatcher.Invoke(DispatcherPriority.Send,新操作(SetImage),decoder.Frames[0]);
}
public void SetImage(图像源)
{
this.BackgroundImage.Source=源;
}
我们在项目中还有一件事。由于ImageSource已放入UI,您必须检查它是否已冻结:
public void SetImage(ImageSource source)
{
ImageSource src = null;
if(!source.IsFrozen)
src = source.GetAsFrozen();
else
src = source;
this.BackgroundImage.Source = src;
}
我已经更新了我的答案,以展示如何做到这一点。谢谢。我会试一试的。我试过背景工作人员,但没用。同样的问题(不幸的是,克隆不起作用,但我得到了一个不同的错误:“异常已被调用的目标抛出。”尝试删除Dispatcher.BeingInvoke部分。BackgroundWorker的整个要点是,事件会自动封送回UI线程,这样您就不必担心了。问题是ImageSource不能保证线程安全,所以一切都会消失。一旦我用BitmapSource替换了它,我的问题就解决了!天哪!我对ImageSource非常失望,以至于我从未在文档中注意到它不是线程安全的。好了。就像Forms/WPF只是STA一样,只是没有很好的文档记录。我想我遇到了一个问题,调用IsFroze不是线程安全的。也就是说,如果它没有被冻结,我会出错。谢谢提醒。我已经升级了bendewey的示例中使用的默认SetImage方法。当然,必须使用Dispatcher.Invoke调用它。在调用Dispatcher.Invoke之前,您最好调用Dispatcher.CheckAccess,如果需要调度,它将返回true;如果可以在不使用Dispatcher的情况下更新UI,则返回false
public void SetImage(ImageSource source)
{
ImageSource src = null;
if(!source.IsFrozen)
src = source.GetAsFrozen();
else
src = source;
this.BackgroundImage.Source = src;
}