尝试从字节数组更新映像时出现跨线程异常

时间:2014-03-10 22:06:09

标签: c# wpf multithreading

我正在开发一个并排显示两个视频的应用程序(不幸的是有自定义格式)。

当我尝试显示视频帧时,我得到“无法访问此对象,因为不同的线程拥有它”-exception,即使我认为我调用调度程序也会解决这个问题。

似乎人们在使用BitmapImage或类似问题时遇到类似的问题,他们可以通过冻结图像来解决问题,但在我的情况下,我收到一个字节数组,似乎没有冻结方法。

这是有问题的代码(从异步方法调用):

private void ShowVideo(string fileName)
{
    ColorVideo video = new ColorVideo(fileName); // ColorVideo reads the frames from disk

    WriteableBitmap image = new WriteableBitmap(video.Width, video.Height, 96, 96, PixelFormats.Bgr32, null);

    // set the image as source for the image frame in the UI
    this.Dispatcher.BeginInvoke((Action)delegate
    {
    this.ImageFrame.Source = image;
    });

    for (int i=0; i < video.Length, i++)
    {
        byte[] frameBytes = video.GetFrame(i);

        // The following line throws the exception.
        // wrapping the line inside a call to Dispatcher doesn't help
        // I get the cross-thread exception regardless

        image.WritePixels(new Int32Rect(0,0,video.Width,video.Height),frameBytes, video.Width*4,0);
    }
}

现在,我怀疑我的方法实际上可能并不理想,无论如何。我想要的只是能够在后台运行showVideo方法并让它更新每秒30次显示的图像。我怎样才能更好地做到这一点?

3 个答案:

答案 0 :(得分:1)

你不能在后台线程中创建WriteableBitmap,而是在Invoke调用中包含它或冻结它(位图,而不是数组!)

此外,您可能不应在此使用BeginInvoke,因为它会延迟执行。稍后,您将继续从后台线程访问该图像。

我得到的印象是你对访问关于线程的GUI元素感到有点困惑 - 你应该首先理清基础知识。

答案 1 :(得分:1)

请注意,您首先创建位图,然后向Dispatcher发出BeginInvoke + setSource操作,然后编写像素。

这会留下一个竞赛:调度程序可以首先将位图设置为源,然后您的循环可能会运行并尝试修改像素。由于循环在后台线程中运行,因此WritePixels可能会抛出。我没有检查,只是一个猜测。

所以,首先尝试:

your-for-loop
{
   create-a-bitmap
   get-frame
   write-pixels

   // XXX

   this.Dispatcher.BeginInvoke((Action)delegate
   {
       this.ImageFrame.Source = image;
   });
}

因此,只需移动调度程序块的开头就可以解决问题,因为它将确保循环不会向已经推送到UI的位图写入任何内容。

在此设置中,您仍然可以在XXX标记处添加“Freeze”,它将允许WPF跳过位图的某些中间副本,可能在渲染期间不会执行单个副本。但是,添加Freeze对于循环将不断分配并丢弃新的位图这一事实无济于事。

创建它们的事实会给内存和GC带来一些压力。不仅会不断创建位图,还必须定期查找并删除GC。您可以通过创建两个位图并通过翻转奇数偶数来重复使用它们来避开它:

create-bitmap-1
create-bitmap-2
your-for-loop
{
   get-frame

   if frame-number is ODD
      write-pixels to bitmap-1
   else
      write-pixels to bitmap-2

   this.Dispatcher.BeginInvoke((Action)delegate
   {
       if frame-number is ODD
           this.ImageFrame.Source = image1;
       else
           this.ImageFrame.Source = image2;
   });
}

注意重要的事情:使用这种方式时,你无法冻结位图来帮助WPF缓存并减少中间副本的数量。您不希望使它们可缓存,并且您需要可以修改位图。它会降低渲染器和/或合成器线程的速度,但会降低整体GC和CPU的压力。

答案 2 :(得分:0)

假设ImageFrame是一个UI控件,您需要ImageFrame.Invoke来设置它的属性.Source。只有创建该控件实例的线程才有权访问和操作它。