我正在尝试写入WritableBitmap
,我想在非UI线程中进行数据处理。
所以我从UI调度程序调用{{1}}和Lock
方法,其余的在不同的线程上完成:
Unlock
可以从任何线程调用此代码,因为它在需要时专门调用UI调度程序。
当我的控制没有受到很大压力时,这就有效。但是,当我几乎立即每100毫秒调用一次时,我会从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();
}));
获得InvalidOperationException
,并显示以下消息:
{“在解锁图像时无法调用此方法。”}
我不明白这是怎么发生的。我的调试输出日志显示我的类的这个实例确实调用了AddDirtyRect
。
更新
我的整个场景:我正在编写一个允许在WPF Lock
控件中叠加浮点矩阵的类。类Image
允许使用API
FloatingPointImageSourceAdapter
它公开了一个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
运行此测试,并且它与上述异常一起崩溃。
更新2
我尝试检查我的命令是否确实按顺序执行。 我添加了另一个私人字段
SleepTime=100
我在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;
}
}
s会抓住这个。
但是柜台上的一切都很好。它们根据串行逻辑正确递增,但我仍然从Debug.Assert
得到例外。
答案 0 :(得分:1)
Application.Current.Dispatcher.Invoke将尝试在UI线程本身上执行作为委托传递的方法,这将在UI线程空闲时发生。如果您尝试连续执行此操作,则几乎不会像在UI线程上执行操作那样。总是在Application.Current.Dispatcher.Invoke上执行的指令应该是非常小的,假设它应该只有一行,就像只更改UI上的值一样。因此,请避免作为部分Dispatcher执行的复杂操作,将其移出调度程序并仅执行更新UI的操作
答案 1 :(得分:0)
所以在经过一些(非常长的)挖掘之后,事实证明真正的错误隐藏在我为了简洁而遗漏的代码中: - )
我的课程允许更改图像的大小。设置数据时,我会检查新尺寸是否与旧尺寸相同,如果我没有初始化新WritableBitmap
。
在while
循环的中间某个时候,图像的大小发生了变化(使用不同的线程)。这导致处理代码的不同阶段处理_mappedBitmap
的不同实例(因为_mappedBitmap
指向不同阶段的不同实例)。因此,当实例更改为新实例时,它将以未锁定状态创建,从而导致(合法)异常。