Question

I'm trying to write into a WritableBitmap and I want to do the data processing in a non-UI thread. So I'm calling the Lock and Unlock methods from the UI dispatcher and the rest is done on a different thread:

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();
}));

This code can be called from any thread, since it specifically calls the UI dispatcher when needed. This works when my control is not under great stress. But when I call this every 100ms almost immediately I get an InvalidOperationException from AddDirtyRect with the following message:

{"Cannot call this method while the image is unlocked."}

I don't understand how this can happen. My Debug Output logs show that Lock indeed has been called for this instance of my class.

UPDATE

My entire scenario: I'm writing a class which will allow diplaying floating-point matrices in a WPF Image control. The class FloatingPointImageSourceAdapter allows setting data using the API

void SetData(float[] data, int width, int height)

And it exposes a ImageSource which an Image control Souce property can be bound to.

Internally this is implemented using WritableBitmap. Whenever a user sets new data I need to process the pixels and rewrite them into the buffer. The data is planned to be set at a high frequency and this is why I went for writing directly into the BackBuffer instead of calling WritePixels. Moreover, since the remapping of the pixels can take a while and the images can be quite large, I want to do the processing on a separate thread.

I have decided to deal with high stress by dropping frames. So I have an AutoResetEvent which keeps track of when the user has requested to update the data. And I have a background task which does the actual work.

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();
            }));
        }
    }
}

I have dropped a lot of code here for the sake of bravity.

My test creates a task which calls SetData within certain intervals:

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);
    }
}

I run this test with ThreadsNumber=1 and with SleepTime=100 and it crashes with the aforementioned exception.

UPDATE 2

I tried checking that my commands indeed execute serially. I added another private field

private int _lockCounter;

And I manipulate it in my while loop:

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;
    }
}

I was hoping that if the message order was somehow messed up my Debug.Asserts would catch this. But everything with the counters is fine. They are incremented correctly according to the serial logic, and still I get the exception from AddDirtyRect.

Was it helpful?

Solution 2

So after some (very long) digging, it turned out the real bug was hidden in the code I left out for the sake of bravity :-)

My class allows changing the size of the image. When setting data, I check if the new size is the same as the old size and if it isn't I initialize a new WritableBitmap.

What happened was that the size of the image was changed (using a different thread) sometime in the middle of the while loop. And this caused different stages of the processing code to process different instances of _mappedBitmap (since _mappedBitmap pointed to different instances throughout the different stages). So when the instance was changed to a new one, it was created in an unlocked state, thus causing the (rightful) exception.

OTHER TIPS

Application.Current.Dispatcher.Invoke will try to execute the method that is passed as delegate to on the UI thread itself, this will happen when UI thread is free. If you try to execute the instructions on this continuously it'll almost nothing like performing the operation on the UI thread. Always the instruction executed on Application.Current.Dispatcher.Invoke should be very minimal say it should be only one line like which shall change only value on the UI nothing more than that. So avoid complex operations that are performed as part Dispatcher, move it out of dispatcher and do the only the operations that updates the UI

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top