锁定和解锁WriteableBitmap

时间:2013-10-27 07:55:11

标签: c# wpf .net-4.0 writablebitmap

我正在尝试写入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得到例外。

2 个答案:

答案 0 :(得分:1)

Application.Current.Dispatcher.Invoke将尝试在UI线程本身上执行作为委托传递的方法,这将在UI线程空闲时发生。如果您尝试连续执行此操作,则几乎不会像在UI线程上执行操作那样。总是在Application.Current.Dispatcher.Invoke上执行的指令应该是非常小的,假设它应该只有一行,就像只更改UI上的值一样。因此,请避免作为部分Dispatcher执行的复杂操作,将其移出调度程序并仅执行更新UI的操作

答案 1 :(得分:0)

所以在经过一些(非常长的)挖掘之后,事实证明真正的错误隐藏在我为了简洁而遗漏的代码中: - )

我的课程允许更改图像的大小。设置数据时,我会检查新尺寸是否与旧尺寸相同,如果我没有初始化新WritableBitmap

while循环的中间某个时候,图像的大小发生了变化(使用不同的线程)。这导致处理代码的不同阶段处理_mappedBitmap的不同实例(因为_mappedBitmap指向不同阶段的不同实例)。因此,当实例更改为新实例时,它将以未锁定状态创建,从而导致(合法)异常。