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
- 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:
- Created 2 years ago
- Reactions:1
- Comments:11 (3 by maintainers)
Top GitHub Comments
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 callingAddDirtyRectangle
(which in most cases does not even have to be blocking, but obviously don’t stall the dispatcher).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.