- Notifications
You must be signed in to change notification settings - Fork 294
Description
Library Versions:
CommunityToolkit.Mvvm Version="8.4.0"
Microsoft.Graphics.Win2D Version="1.3.2"
Microsoft.WindowsAppSDK Version="1.7.250606001"
Description
I have a WinUI 3 page that features a real-time preview of a window. This is achieved by capturing the window content at
~30 FPS, creating a CanvasBitmap from each capture, and drawing it onto a CanvasControl.
When the real-time preview is stopped, I perform a thorough cleanup of all resources. However, a significant amount of
memory is never fully released back to the system, and remains allocated for the application's lifetime.
Steps to Reproduce
- A
CanvasControlis defined in XAML, with itsDrawevent subscribed (Draw="DesktopCanvas_Draw"). - A
DispatcherTimerticks every 30ms, calling a method to refresh the screen capture. - The
RefreshCaptureAsyncmethod creates a newCanvasBitmapand disposes the previous one.
DesktopCanvas.Invalidate()is called to trigger a redraw. - The
DesktopCanvas_Drawmethod draws the latestCanvasBitmapwith scaling. - After running the preview for a few seconds, a button is clicked to stop it.
- The cleanup logic in
CaptureDesktopButton_Clickstops the timer, disposes the final bitmap, and attempts a thorough
cleanup.
Code Snippets
Here is the relevant code from my implementation:
DeskTopCapturePage.xaml:
<Grid> <!-- Other elements --> <canvas:CanvasControl x:Name="DesktopCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Draw="DesktopCanvas_Draw" /> <!-- Other elements --> <StackPanel Grid.Row="2"> <Button x:Name="CaptureDesktopButton" HorizontalAlignment="Center" Click="CaptureDesktopButton_Click" Content="开始预览" /> <!-- Other elements --> </StackPanel> </Grid>DeskTopCapturePage.xaml.cs:
public sealed partial class DeskTopCapturePage : Page { private DeskTopCaptureViewModel viewModel = new(); public DeskTopCapturePage() { this.InitializeComponent(); viewModel.timer.Interval = TimeSpan.FromMilliseconds(30); viewModel.timer.Tick += async (s, e) => await RefreshCaptureAsync(); } private async Task RefreshCaptureAsync() { using (var softwareBitmap = GetDesktop.CaptureWindow()) // This method provides a capture { if (softwareBitmap != null) { viewModel.latestBitmap?.Dispose(); viewModel.latestBitmap = CanvasBitmap.CreateFromSoftwareBitmap(CanvasDevice.GetSharedDevice(), softwareBitmap); DesktopCanvas.Invalidate(); } } } private void DesktopCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args) { if (viewModel.latestBitmap != null) { // Drawing logic with scaling... args.DrawingSession.DrawImage(viewModel.latestBitmap, ...); } } private void CaptureDesktopButton_Click(object sender, RoutedEventArgs e) { if (viewModel.timer.IsEnabled) { // --- STOP PREVIEW & CLEANUP --- viewModel.timer.Stop(); CaptureDesktopButton.Content = "开始预览"; viewModel.latestBitmap?.Dispose(); viewModel.latestBitmap = null; DesktopCanvas.Invalidate(); // Attempting thorough cleanup CanvasDevice.GetSharedDevice().Trim(); if(DesktopCanvas != null) { // This cleanup step was tested, but did not solve the final memory retention // DesktopCanvas.RemoveFromVisualTree(); // ((Grid)DesktopCanvas.Parent).Children.Remove(DesktopCanvas); // DesktopCanvas = null; } GC.Collect(); GC.WaitForPendingFinalizers(); } else { // --- START PREVIEW --- viewModel.timer.Start(); CaptureDesktopButton.Content = "停止预览"; } } }📊 Key Observations
| State | Memory Usage |
|---|---|
| Initial state | ~70 MB |
| During preview | ~130–170 MB |
| After cleanup | Still ~130 MB |
❗ Even after full cleanup, memory does not return to the initial level. This suggests that some internal resources are retained by
CanvasDeviceorCanvasControland not being fully released.
🤔 Expected Behavior
After stopping the preview and performing cleanup, the app's memory usage should return close to the initial level (~70–90 MB).
😟 Actual Behavior
Despite calling Dispose(), Trim(), and even removing the CanvasControl from the visual tree, memory usage stabilizes around ~130 MB and does not drop further. Subsequent preview start/stop cycles do not increase memory usage, indicating that no new leaks occur — but also that the initial allocation is never freed.
🧪 Additional Notes
We noticed an odd behavior:
If I subscribe to the Draw event twice, memory usage goes up to ~100–170 MB during preview, and then drops to ~100 MB after cleanup.But after starting the computer the next day, it failed again, but at least this told me that there should be memory space that can be optimized.
🧰 What I’m Asking For
I would like guidance or solutions on how to:
✅ Fully release all Win2D-related resources (especially CanvasDevice, CanvasBitmap)
✅ Ensure that memory returns to the pre-preview baseline
✅ Avoid any hidden references or caches that prevent memory from being reclaimed
Any help with debugging tools, profiling steps, or best practices for resource management in WinUI 3 + Win2D would be greatly appreciated.