我的应用程序运行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而不进行任何无用的缓冲区复制/克隆?
答案 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所说的,我建议采用以下方法:
通过ThreadPool在QueueUserWorkItem线程中的单独缓冲区(例如byte[]
)上执行异步“位图编辑”代码。每次需要执行异步操作时都不要创建新的线程。这就是ThreadPool的用途。
在WriteableBitmap的Dispatcher中复制WritePixels编辑的缓冲区。无需锁定/解锁。
示例:
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
是带图像的控件/窗口。