-
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.