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

我的应用程序运行CPU重的algorythms编辑放置在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线程(即MSDN )中执行BackBuffer版本。

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

MSDN建议在后台线程中写入后备缓冲区。 只需要在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)完成Clemens建议时,在缓冲区和blit中进行编辑。

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

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

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

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

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

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

例:

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

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

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

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

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

基于这个答案 ,我实现了以下内容:

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

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

使用属性意味着如果WritableBitmap发生更改(例如由于分辨率),它将在View中更新,并且还将构造新的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); 

可能有更好的function……

在同一个post中,复制后:

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

其中parent是带有Image的Control / Window。