question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Window freezes during resize when updating WriteableBitmap from worker thread

See original GitHub issue
  • .NET Core Version: (e.g. 3.0 Preview1, or daily build number, use dotnet --info) 6.0.100

  • Windows version: (winver) Windows 10 21H2

  • Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes/No Yes

  • Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc…)? If yes, please file the issue via the instructions here. No

Problem description: Updating the back buffer of a WriteableBitmap once per frame works fine until I try to resize the form. As soon as I do that, the mouse cursor is forever in resize mode and the mouse is locked out from the Windows task bar.

Actual behavior: Stuck in resize mode, Window does not respond anymore.

Expected behavior: Writing to a WriteableBitmap should not disturb the Window’s resizing behavior because it was explicitly designed for multi-threading: https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.writeablebitmap?view=windowsdesktop-6.0&viewFallbackFrom=net-6.0#remarks

  1. Write changes to the back buffer. Other threads may write changes to the back buffer when the WriteableBitmap is locked.

Minimal repro: Create a new Wpf App, then copy the following code over MainWindow.xaml.cs:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        readonly byte[] buffer = new byte[1920 * 1080 * 3];

        readonly WriteableBitmap wb = new WriteableBitmap(
            1920, 1080, 96, 96, PixelFormats.Bgr24, null);

        readonly Dispatcher dispatcher;
        Thread thread;

        public MainWindow()
        {
            InitializeComponent();

            // set all pixels to blue
            for (int y = 0; y < wb.PixelHeight; ++y)
            {
                for (int x = 0; x < wb.PixelWidth; ++x)
                {
                    int offset = 3 * x + (y * wb.PixelWidth * 3);
                    buffer[offset + 0] = 0xFF;
                }
            }

            Image image = new Image() { Source = wb };
            this.Content = image;

            dispatcher = Dispatcher;
            Loaded += MainWindow_Loaded;
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            thread = new Thread(ThreadFunc);
            thread.Start();
        }

        void ThreadFunc()
        {
            for (; ; )
            {
                IntPtr backBuffer = IntPtr.Zero;


                // lock on UI thread
                dispatcher.Invoke(() =>
                {
                    wb.Lock();
                    
                    // we have to copy this because it must only be accessed from the UI thread
                    backBuffer = wb.BackBuffer;

                    // assume there is no padding to make this example short
                    System.Diagnostics.Debug.Assert(wb.BackBufferStride == wb.PixelWidth * wb.Format.BitsPerPixel / 8);
                });
                System.Diagnostics.Debug.Assert(backBuffer != IntPtr.Zero);


                // update on this thread
                Marshal.Copy(buffer, 0, backBuffer, buffer.Length);


                // unlock on UI thread
                dispatcher.Invoke(() =>
                {
                    wb.AddDirtyRect(new Int32Rect(0, 0, wb.PixelWidth, wb.PixelHeight));
                    wb.Unlock();
                });
            }
        }
    }
}

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:1
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
miloushcommented, Jan 17, 2022

Well the example on the same page you cited does not really suggest it is safe to allow other dispatcher operations during the lock. You might want to prepare your frame in another buffer and then perform just a memory copy inside the lock on UI thread.

Alternatively, in case tearing was not an issue for your application, you might not need to keep the bitmap locked, only lock it for obtaining the BackBuffer (which contractually never changes so you can even do that only once) and then for calling AddDirtyRectangle (which in most cases does not even have to be blocking, but obviously don’t stall the dispatcher).

0reactions
andrekoehlercommented, Jun 16, 2023

Copying the data on the UI thread is not a good idea IMO. For 4k video the mem copy alone takes 15 milliseconds, so that means 45% of the time your UI thread would be locked. UI thread should be locked 0% of the time.

Not to mention, what is the point of a “Lock” feature that only works on one thread. Maybe for WPF threads? Idk, just seems weird to me.

I absolutely agree with you. The only improvement I found up to now is to make the copy (on the UI thread) as fast as possible by pinvoking the MFCopyImage function from the Media Foundation DLL: Doc IIRC this was faster than several other copy-methods I tried.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cross-thread exception when trying to update image from ...
It seems people have had similar problems when using BitmapImage s or similar, and that they could solve the problem via freezing the...
Read more >
Office products - display freezing. Window resize refreshes it.
If I change the size of the window the display catches up immediately. But it will freeze again after the next change until...
Read more >
Using async / await still results in my UI freezing when ...
I tried calling the method containing this image scaler with await Task.Run and await Task.Factory.StartNew with longrunning as an option. I ...
Read more >
Total freeze up when resize window.
In a standalone build, clicking on "Size" on the top left menu and then resizing leads to a total freeze of the application....
Read more >
Untitled
Lagu terbaru om monata mp3, Lexmark 2500 driver update, Pojkarna som busar remix ... Oddisee the beauty in all zippy, Alim 6 0...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found