You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:多屏托盘窗口关闭时内存泄漏(RAM占用持续上涨)

内存泄漏问题:窗口创建关闭后RAM占用持续上涨

我的程序核心功能是通过托盘为所有屏幕创建窗口,托盘关闭时主程序流程终止。当前遇到内存泄漏问题:每次创建并关闭窗口后内存未被有效清理,导致RAM占用持续上涨。即使手动调用GC,情况也未得到明显改善,不使用GC的话情况会更糟。


TrayIconViewModel 代码

public class TrayIconViewModel : INotifyPropertyChanged
{
    // Commands for user interactions
    public ICommand CreateWindowCommand { get; }
    public ICommand ExitCommand { get; }

    private bool _disposed;

    public TrayIconViewModel()
    {
        // Initializing commands
        CreateWindowCommand = new RelayCommand(async () => await CreateWindowAsync());
        ExitCommand = new RelayCommand(ExitApp);
    }

    // Asynchronous method to create a new window
    private async Task CreateWindowAsync()
    {
        await WindowManager.Instance.CreateAndShowWindowAsync();
    }

    // Method to terminate the application
    private void ExitApp()
    {
        // Gracefully shut down the application using the desktop lifetime instance
        var desktopLifetime = Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
        desktopLifetime?.Shutdown(); // Gracefully shut down the application
        Environment.Exit(0); // Forcibly terminate the process (if needed)
    }

    // Implementation of INotifyPropertyChanged to notify UI about property changes
    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

ScreenManager 代码

public sealed class ScreenManager
{
    // Singleton instance of ScreenManager
    private static readonly Lazy<ScreenManager> Lazy = new(() => new ScreenManager());
    public static ScreenManager Instance => Lazy.Value;

    private ScreenManager() { }

    // Retrieve all screens available without caching as they may change
    public List<Screen> GetAllScreens()
    {
        List<Screen> screens;
        var tempWindow = new MainWindow(); // Temporary window to access screens
        try
        {
            screens = tempWindow.Screens.All.ToList();
            if (screens.Count == 0)
            {
                throw new InvalidOperationException("Unable to retrieve screen information.");
            }
        }
        finally
        {
            tempWindow.Close(); // Always close the temporary window
        }
        return screens;
    }
}

WindowManager 代码

public sealed class WindowManager : IDisposable
{
    // Singleton instance of WindowManager
    private static readonly Lazy<WindowManager> Lazy = new(() => new WindowManager());
    public static WindowManager Instance => Lazy.Value;

    // Weak reference to the main window to avoid memory leaks
    private WeakReference<MainWindow>? _mainWindowRef;
    private bool _disposed;

    // Structure to track window state (open/closed)
    private struct WindowState
    {
        public bool IsOpen;
        public bool EventsUnsubscribed;
    }

    private WindowState _windowState;

    // Destructor to ensure proper disposal
    ~WindowManager()
    {
        Dispose(false);
    }

    // Method to create and show the main window asynchronously
    public async Task CreateAndShowWindowAsync()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(WindowManager));

        if (!_windowState.IsOpen || (_mainWindowRef != null && !_mainWindowRef.TryGetTarget(out _)))
        {
            await Dispatcher.UIThread.InvokeAsync(() =>
            {
                // Get all screens and find the main screen
                var screens = ScreenManager.Instance.GetAllScreens();
                var mainScreen = screens.FirstOrDefault(s => s.Bounds.X <= 0 && s.Bounds.Y <= 0)
                                 ?? throw new InvalidOperationException("Main screen not found.");

                // Combine screen bounds to get the total screen space
                var combinedBounds = screens.Aggregate(new PixelRect(), (current, screen) => current.Union(screen.Bounds));

                // Create and initialize the new window
                var newWindow = new MainWindow();
                InitializeWindow(newWindow, combinedBounds, mainScreen);
                SubscribeToWindowEvents(newWindow);

                // Save the window reference and mark it as open
                _mainWindowRef = new WeakReference<MainWindow>(newWindow);
                newWindow.Show();
                _windowState.IsOpen = true;
                _windowState.EventsUnsubscribed = false;

                Console.WriteLine("Window successfully created and displayed.");
            });
        }
        else if (_mainWindowRef != null && _mainWindowRef.TryGetTarget(out var existingWindow))
        {
            await Dispatcher.UIThread.InvokeAsync(() => existingWindow.Activate());
        }
    }

    // Method to initialize window properties and position
    private void InitializeWindow(MainWindow window, PixelRect bounds, Screen mainScreen)
    {
        window.Width = bounds.Width / mainScreen.PixelDensity;
        window.Height = bounds.Height / mainScreen.PixelDensity;
        window.Position = new PixelPoint(bounds.X, bounds.Y);
        window.Topmost = true;
        window.ExtendClientAreaToDecorationsHint = true;
        window.ExtendClientAreaTitleBarHeightHint = -1;
        window.CanResize = false;
        window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
    }

    // Subscribe to window events like close and keypress
    private void SubscribeToWindowEvents(MainWindow window)
    {
        window.Closed += OnWindowClosed;
        window.KeyDown += MainWindow_KeyDown;
        Console.WriteLine("Subscribed to window events.");
    }

    // Unsubscribe from window events to prevent memory leaks
    private void UnsubscribeFromWindowEvents(MainWindow? window)
    {
        if (window != null && !_windowState.EventsUnsubscribed)
        {
            window.Closed -= OnWindowClosed;
            window.KeyDown -= MainWindow_KeyDown;
            _windowState.EventsUnsubscribed = true;
            Console.WriteLine("Unsubscribed from window events.");
        }
    }

    // Event handler for window close event
    private void OnWindowClosed(object? sender, EventArgs e)
    {
        CloseWindow();
    }

    // Event handler for key press event (closes window on Escape)
    private void MainWindow_KeyDown(object? sender, KeyEventArgs e)
    {
        if (e.Key == Key.Escape)
        {
            CloseWindow();
        }
    }

    // Method to close the window and clean up resources
    private void CloseWindow()
    {
        if (_windowState.IsOpen && _mainWindowRef != null && _mainWindowRef.TryGetTarget(out var window))
        {
            Dispatcher.UIThread.InvokeAsync(() =>
            {
                UnsubscribeFromWindowEvents(window);
                window.Close();
            });

            _windowState.IsOpen = false;
            DisposeWindow();

            // Force garbage collection to clean up resources
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Console.WriteLine("Garbage collection performed after window closure.");
        }
    }

    // Method to dispose of the window and release resources
    private void DisposeWindow()
    {
        if (_mainWindowRef != null && _mainWindowRef.TryGetTarget(out var window))
        {
            UnsubscribeFromWindowEvents(window);
            window.Close();
            (window as IDisposable)?.Dispose(); // Explicitly dispose resources
            _mainWindowRef = null;
            Console.WriteLine("Window references cleared.");
        }

        _windowState.IsOpen = false;
    }

    // Dispose pattern implementation to clean up resources
    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                DisposeWindow();
            }

            _disposed = true;
        }
    }

    // Public method to dispose resources
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

内容的提问来源于stack exchange,提问作者callsign_332

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:14:54