首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >锁定和解锁WritableBitmap

锁定和解锁WritableBitmap
EN

Stack Overflow用户
提问于 2013-10-27 07:55:10
回答 2查看 759关注 0票数 0

我正在尝试写入一个WritableBitmap,并且我希望在一个非UI线程中进行数据处理。因此,我从UI调用LockUnlock方法,其余的在另一个线程上完成:

代码语言:javascript
复制
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设置数据。

代码语言:javascript
复制
void SetData(float[] data, int width, int height)

并且它公开了一个ImageSourceImage控件Souce属性可以绑定到该属性。

在内部,这是使用WritableBitmap实现的。每当用户设置新数据时,我都需要处理像素并将它们重写到缓冲区中。计划将数据设置为高频,这就是为什么我选择直接写入BackBuffer,而不是调用WritePixels。此外,由于像素的重新映射可能需要一段时间,而且图像可能相当大,所以我想在一个单独的线程上进行处理。

我已经决定通过放下框架来处理高压力。因此,我有一个AutoResetEvent,它跟踪用户何时要求更新数据。我有一个背景任务来做实际的工作。

代码语言:javascript
复制
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的任务:

代码语言:javascript
复制
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=1SleepTime=100上运行了这个测试,它在上述异常下崩溃了。

更新2

我试着检查我的命令是否确实是串行执行的。我增加了另一个私人领域

代码语言:javascript
复制
private int _lockCounter;

我在我的while循环中操纵它:

代码语言:javascript
复制
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获得异常。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-10-27 12:08:22

因此,经过(很长时间)的挖掘,发现真正的bug隐藏在我为了勇敢起见而遗漏的代码中:-)

我的类允许更改图像的大小。在设置数据时,我检查新大小是否与旧大小相同,如果不是,则初始化一个新的WritableBitmap

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

票数 0
EN

Stack Overflow用户

发布于 2013-10-27 09:00:32

Application.Current.Dispatcher.Invoke将尝试在UI线程本身上执行作为委托传递给的方法,这将在UI线程空闲时发生。如果您试图连续地执行这方面的指令,几乎没有什么比在UI线程上执行操作更好的了。在Application.Current.Dispatcher.Invoke上执行的指令总是应该是非常小的,比方说,它应该是像这样的一行,它只会改变UI上的值,仅此而已。因此,避免作为部分dispatcher执行的复杂操作,将其移出Dispatcher,只执行更新UI的操作。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19615852

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档