我正在尝试写入 aWritableBitmap
并且我想在非 UI 线程中进行数据处理。所以我从 UI 调度程序调用Lock
andUnlock
方法,其余的在不同的线程上完成:
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
// Long processing straight on pBackBuffer...
Application.Current.Dispatcher.Invoke(new Action(()=>
{
Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
可以从任何线程调用此代码,因为它会在需要时专门调用 UI 调度程序。当我的控制压力不大时,这很有效。但是当我几乎立即每 100 毫秒调用一次时,我会收到InvalidOperationException
来自AddDirtyRect
以下消息的消息:
{"图像解锁时无法调用此方法。"}
我不明白这怎么会发生。我的调试输出日志显示,Lock
我的班级的这个实例确实被调用了。
更新
Image
我的整个场景:我正在编写一个允许在 WPF控件中显示浮点矩阵的类。该类FloatingPointImageSourceAdapter
允许使用 API 设置数据
void SetData(float[] data, int width, int height)
它公开了ImageSource
一个Image
控件Souce
属性可以绑定到的。
在内部,这是使用WritableBitmap
. 每当用户设置新数据时,我都需要处理像素并将它们重写到缓冲区中。数据计划以高频率设置,这就是为什么我直接写入BackBuffer
而不是调用WritePixels
. 此外,由于像素的重新映射可能需要一段时间并且图像可能非常大,我想在单独的线程上进行处理。
我决定通过丢帧来应对高压力。所以我有一个AutoResetEvent
跟踪用户何时请求更新数据的方法。我有一个后台任务来完成实际工作。
class FloatingPointImageSourceAdapter
{
private readonly AutoResetEvent _updateRequired = new AutoResetEvent(false);
public FloatingPointImageSourceAdapter()
{
// all sorts of initializations
Task.Factory.StartNew(UpdateImage, TaskCreationOptions.LongRunning);
}
public void SetData(float[] data, int width, int height)
{
// save the data
_updateRequired.Set();
}
private void UpdateImage()
{
while (true)
{
_updateRequired.WaitOne();
Debug.WriteLine("{1}: Update requested from thread {2}, {0}", DateTime.Now, this.GetHashCode(), Thread.CurrentThread.ManagedThreadId);
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
// The processing of the back buffer
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
}
}
}
为了勇敢起见,我在这里删除了很多代码。
我的测试创建了一个SetData
在特定时间间隔内调用的任务:
private void Button_Click_StartStressTest(object sender, RoutedEventArgs e)
{
var sleepTime = SleepTime;
_cts = new CancellationTokenSource();
var ct = _cts.Token;
for (int i = 0; i < ThreadsNumber; ++i)
{
Task.Factory.StartNew(() =>
{
while (true)
{
if (ct.IsCancellationRequested)
{
break;
}
int width = RandomGenerator.Next(10, 1024);
int height = RandomGenerator.Next(10, 1024);
var r = new Random((int)DateTime.Now.TimeOfDay.TotalMilliseconds);
var data = Enumerable.Range(0, width * height).Select(x => (float)r.NextDouble()).ToArray();
this.BeginInvokeInDispatcherThread(() => FloatingPointImageSource.SetData(data, width, height));
Thread.Sleep(RandomGenerator.Next((int)(sleepTime * 0.9), (int)(sleepTime * 1.1)));
}
}, _cts.Token);
}
}
ThreadsNumber=1
我使用and with运行此测试,SleepTime=100
它因上述异常而崩溃。
更新 2
我尝试检查我的命令是否确实是串行执行的。我添加了另一个私有字段
private int _lockCounter;
我在我的while
循环中操纵它:
private void UpdateImage()
{
while (true)
{
_updateRequired.WaitOne();
Debug.Assert(_lockCounter == 0);
_lockCounter++;
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.Assert(_lockCounter == 1);
++_lockCounter;
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
Debug.Assert(pBackBuffer != IntPtr.Zero);
Debug.Assert(_lockCounter == 2);
++_lockCounter;
// Process back buffer
Debug.Assert(_lockCounter == 3);
++_lockCounter;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.Assert(_lockCounter == 4);
++_lockCounter;
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
Debug.Assert(_lockCounter == 5);
_lockCounter = 0;
}
}
我希望如果消息顺序以某种方式搞砸了,我Debug.Assert
的 s 会抓住这个。但是柜台的一切都很好。它们根据串行逻辑正确递增,但我仍然从AddDirtyRect
.