C# 如何在后台加载图像?

C# 如何在后台加载图像?,c#,wpf,image,delegates,C#,Wpf,Image,Delegates,我正在尝试在后台加载图像,然后更新UI。我已经玩了一整天,我不知道我错过了什么。我不断得到以下错误: “调用线程无法访问此对象,因为其他线程拥有它。” 我在下面一个又一个例子中搜寻,但似乎找不到答案。我还将触摸UI的代码包装到另一个BeginInvoke中 更新3:故事的寓意。ImageSource对于访问来说不是线程安全的 更新2:这必须是一个简单的解决方案:)。我尝试了克隆,但没有成功,但我得到了一个不同的错误:“异常已被调用的目标抛出。” 更新1:我尝试了BackgroundWorker,

我正在尝试在后台加载图像,然后更新UI。我已经玩了一整天,我不知道我错过了什么。我不断得到以下错误:

“调用线程无法访问此对象,因为其他线程拥有它。”

我在下面一个又一个例子中搜寻,但似乎找不到答案。我还将触摸UI的代码包装到另一个BeginInvoke中

更新3:故事的寓意。ImageSource对于访问来说不是线程安全的

更新2:这必须是一个简单的解决方案:)。我尝试了克隆,但没有成功,但我得到了一个不同的错误:“异常已被调用的目标抛出。”

更新1:我尝试了BackgroundWorker,但仍然得到相同的错误,但它发生在brush.ImageSource.Height上。我是否正确地向用户界面发送信号?有什么建议吗

这是我的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>

您希望在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;
}