如何在非UI线程中编辑WriteableBitmap.BackBuffer?

时间:2012-03-26 08:37:36

标签: c# .net wpf

我的应用程序运行CPU密集的算法来编辑放置在WPF窗口的图像。我需要在后台线程中完成编辑。但是,尝试在非UI线程中编辑WritableBitmap的BackBuffer会引发InvalidOperationException。

    private WriteableBitmap writeableBitmap;

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        // Create WritableBitmap in UI thread.
        this.writeableBitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgr24, null);
        this.image1.Source = this.writeableBitmap;

        // Run code in non UI thread.
        new Thread(
            () =>
            {
                // 'Edit' bitmap in non UI thread.
                this.writeableBitmap.Lock(); // Exception: The calling thread cannot access this object because a different thread owns it.

                // ... At this place the CPU is highly loaded, we edit this.writeableBitmap.BackBuffer.

                this.writeableBitmap.Unlock();
            }).Start();
    }

我已阅读了数十本手册,所有这些手册都告诉我在UI线程中进行BackBuffer版本(即MSDN)。

如何在非UI线程中编辑WritableBitmap.BackBuffer而不进行任何无用的缓冲区复制/克隆?

5 个答案:

答案 0 :(得分:11)

MSDN suggests在后台线程中写回后台缓冲区。只需要在UI线程上执行某些更新前和更新后操作。因此,当后台线程正在进行实际更新时,UI线程可以自由地执行其他操作:

        //Put this code in a method that is called from the background thread
        long pBackBuffer = 0, backBufferStride = 0;
        Application.Current.Dispatcher.Invoke(() =>
        {//lock bitmap in ui thread
            _bitmap.Lock();
            pBackBuffer = (long)_bitmap.BackBuffer;//Make pointer available to background thread
            backBufferStride = Bitmap.BackBufferStride;
        });
        //Back to the worker thread
        unsafe
        {
            //Carry out updates to the backbuffer here
            foreach (var update in updates)
            {
                long bufferWithOffset = pBackBuffer + GetBufferOffset(update.X, update.Y, backBufferStride);
                *((int*)bufferWithOffset) = update.Color;
            }
        }
        Application.Current.Dispatcher.Invoke(() =>
        {//UI thread does post update operations
            _bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, width, height));
            _bitmap.Unlock();
        });

答案 1 :(得分:3)

正如克莱门斯所说,这是不可能的。

您有三种选择:

1)在Clemens建议完成后,在缓冲区中进行编辑并进行blit。

2)以非常小的块进行编辑,并在GUI线程上以优先级安排它们。如果你的工作块足够小,GUI将保持响应,但显然这会使编辑代码变得复杂。

3)结合1& 2.在另一个线程中编辑小块,然后在完成时对每个块进行blit。这样可以保持GUI响应,而无需使用内存作为完整的后台缓冲区。

答案 2 :(得分:2)

你根本无法从非UI线程写入BackBuffer。

除了Klaus78所说的,我建议采用以下方法:

  1. 通过ThreadPoolQueueUserWorkItem线程中的单独缓冲区(例如byte[])上执行异步“位图编辑”代码。每次需要执行异步操作时都不要创建新的线程。这就是ThreadPool的用途。

  2. 在WriteableBitmap的Dispatcher中复制WritePixels编辑的缓冲区。无需锁定/解锁。

  3. 示例:

    private byte[] buffer = new buffer[...];
    
    private void UpdateBuffer()
    {
        ThreadPool.QueueUserWorkItem(
            o =>
            {
                // write data to buffer...
                Dispatcher.BeginInvoke((Action)(() => writeableBitmap.WritePixels(..., buffer, ...)));
            });
    }
    

答案 3 :(得分:0)

在WPF中,使用Dispatcher类完成交叉线程调用。

在无UI线程的情况下,您需要获取创建WritableBitmap的线程的Dispatcher实例。

在该调度程序上然后调用Invoke(如果你想让它异步,则调用BeginInvoke)

Invoke然后调用编辑BackBuffer的委托函数

答案 4 :(得分:0)

我根据this answer实施了以下内容:

在视图模型中,有一个这样的属性,它绑定到XAML中的Image源:

private WriteableBitmap cameraImage;
private IntPtr cameraBitmapPtr;
public WriteableBitmap CameraImage
{
    get { return cameraImage; }
    set
    {
        cameraImage = value;
        cameraBitmapPtr = cameraImage.BackBuffer;
        NotifyPropertyChanged();
    }
}

使用属性意味着如果WritableBitmap发生变化,例如由于分辨率,它将在视图中更新,并且还将构建新的IntPtr。

适当时构建图像:

CameraImage = new WriteableBitmap(2448, 2048, 0, 0, PixelFormats.Bgr24, null);

在更新主题中,将新图像复制到例如使用:

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
你会做的

CopyMemory(cameraImagePtr, newImagePtr, 2448 * 2048 * 3);

这可能有更好的功能......

在同一个帖子中,复制后:

parent.Dispatcher.Invoke(new Action(() =>
{
    cameraImage.Lock();
    cameraImage.AddDirtyRect(new Int32Rect(0, 0, cameraImage.PixelWidth, cameraImage.PixelHeight));
    cameraImage.Unlock();
}), DispatcherPriority.Render);

其中parent是带图像的控件/窗口。