C# 如何在非UI线程中编辑WritableBitmap.BackBuffer?
我的应用程序运行CPU密集型算法来编辑放置在WPF窗口中的图像。我需要在一个背景线程的版本完成。但是,尝试在非UI线程中编辑可写位图的BackBuffer会引发InvalidOperationExceptionC# 如何在非UI线程中编辑WritableBitmap.BackBuffer?,c#,.net,wpf,C#,.net,Wpf,我的应用程序运行CPU密集型算法来编辑放置在WPF窗口中的图像。我需要在一个背景线程的版本完成。但是,尝试在非UI线程中编辑可写位图的BackBuffer会引发InvalidOperationException private WriteableBitmap writeableBitmap; private void button1_Click(object sender, RoutedEventArgs e) { // Create WritableB
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版本(即
如何在非UI线程中编辑WritableBitmap.BackBuffer,而不进行任何无用的缓冲区复制/克隆?在WPF中,跨线程调用是使用Dispatcher类完成的 在非UI线程中,需要获取创建WritableBitmap的线程的调度程序实例 然后在该调度器上调用Invoke(如果希望异步,则调用BeginInvoke)
Invoke然后调用一个委托函数,其中BackBuffer是编辑的除了Klaus78所说的,我建议采用以下方法:
字节[]
)上执行异步“位图编辑”代码。不要每次需要执行异步操作时都创建新线程。这就是ThreadPool的用途
private byte[] buffer = new buffer[...];
private void UpdateBuffer()
{
ThreadPool.QueueUserWorkItem(
o =>
{
// write data to buffer...
Dispatcher.BeginInvoke((Action)(() => writeableBitmap.WritePixels(..., buffer, ...)));
});
}
正如克莱门斯所说,这是不可能的 你有三个选择: 1) 按照克莱门斯的建议,在缓冲区中进行编辑,并在完成后进行blit 2) 在非常小的块中进行编辑,并在GUI线程上以很好的优先级安排它们。如果您将工作块保持足够小,GUI将保持响应,但显然这会使编辑代码复杂化 3) 组合1和2。在另一个线程中编辑小块,然后在每个块完成时进行blit。这可以保持GUI的响应速度,而无需使用内存作为完整的后台缓冲区。在后台线程中写入后台缓冲区。只需要在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();
});
我实施了以下内容,基于: 在视图模型中,有这样一个属性,它绑定到XAML中的图像源:
private WriteableBitmap cameraImage;
private IntPtr cameraBitmapPtr;
public WriteableBitmap CameraImage
{
get { return cameraImage; }
set
{
cameraImage = value;
cameraBitmapPtr = cameraImage.BackBuffer;
NotifyPropertyChanged();
}
}
使用属性意味着,如果可写位图发生更改(例如,由于分辨率的原因),它将在视图中更新,并且还将构造一个新的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
是带有图像的控件/窗口。我在我的问题中发布了new Thread()
,只是想暗示操作应该在其他(随机)线程中完成。在我的问题中,我还声明我不想创建任何缓冲区,但我忘了提到我的位图是huuuge。这就是为什么你的方法有点不符合我的需要。:)是的,但是我恐怕你的问题的答案是:你不能。我认为BackBuffer是专门为后台编辑操作而存在的,但是微软的决定不同。无论如何谢谢你。我不会说“简直不能”,因为你实际上可以:)看我的代码回复。但委托函数将在哪个线程中执行?UI线程?线程池中的一些随机线程?还是什么?是的。第二种方法很难实现,而第一种方法很容易实现,因为它有一个快速而好的Blit函数。第三种方法当然是最好的,但是我没有时间对我们的应用程序进行那么多的增强。建议1没有奏效,因为Dispatcher.BeginInvoke(…writeableBitmap.WritePixels(…,buffer,…)代码>花费太多时间。结果,应用程序出现了很多小故障。此外,在UI线程中执行CPU密集型任务看起来更具响应性。现在我将尝试使用两个常规位图重新实现可写位图。我认为这不是正确的方法。这两个调用的阻塞时间将超过写入缓冲区所需的时间。我完全同意像素缓冲区的更新是一个快速过程的情况。如果缓冲区更新涉及一些昂贵的计算(即成本超过两个调用),那么上述方法仍然是一种完全可行的方法。