我正在尝试写入一个WritableBitmap,并且我希望在一个非UI线程中进行数据处理。因此,我从UI调用Lock和Unlock方法,其余的在另一个线程上完成:
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 dispatcher。当我的控制没有承受很大压力时,这是可行的。但是,当我几乎立即每100 an调用它一次时,我就会从InvalidOperationException得到一个AddDirtyRect,其中包含以下消息:
{“无法在图像解锁时调用此方法”}
我不明白这怎么会发生。我的调试输出日志显示,确实已经为我的类的这个实例调用了Lock。
更新
我的整个场景:我正在编写一个类,它允许在WPF Image控件中使用浮点矩阵。类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和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就会发现这一点。但是有柜台的一切都很好。它们根据串行逻辑正确递增,但我仍然从AddDirtyRect获得异常。
发布于 2013-10-27 12:08:22
因此,经过(很长时间)的挖掘,发现真正的bug隐藏在我为了勇敢起见而遗漏的代码中:-)
我的类允许更改图像的大小。在设置数据时,我检查新大小是否与旧大小相同,如果不是,则初始化一个新的WritableBitmap。
所发生的情况是,在while循环中间的某个时间改变了映像的大小(使用不同的线程)。这导致处理代码的不同阶段处理_mappedBitmap的不同实例(因为_mappedBitmap在不同阶段指向不同的实例)。因此,当实例被更改为新实例时,它是在未锁定状态下创建的,从而导致(合法的)异常。
发布于 2013-10-27 09:00:32
Application.Current.Dispatcher.Invoke将尝试在UI线程本身上执行作为委托传递给的方法,这将在UI线程空闲时发生。如果您试图连续地执行这方面的指令,几乎没有什么比在UI线程上执行操作更好的了。在Application.Current.Dispatcher.Invoke上执行的指令总是应该是非常小的,比方说,它应该是像这样的一行,它只会改变UI上的值,仅此而已。因此,避免作为部分dispatcher执行的复杂操作,将其移出Dispatcher,只执行更新UI的操作。
https://stackoverflow.com/questions/19615852
复制相似问题