Click here to Skip to main content
15,879,683 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
hi How to solve splash window's memory leak
please look at code in tried section.

I run application
then FirstWindow opens
in Diagnustic Tools Memory Usage tab, Take a Snapshot
then open SecondWindow
splashWindows displays great
and second window opens
splash windows get closed
take another snapshot
in diff view LoadingWindow is remained and causes memory leak!

in class App we have public ISplashScreen splashScreen;
LoadingWindow is drived from Window and implemented from ISplashScreen
this is splash window that is shown from FristWindow
and will be closed in second windows when its load completed

I write this code by searching in the internet but I do not understand dispatcher and threading and dependency well
I don't know why it has memory leak
Thank You in advance

What I have tried:

I create a wpf framework app by the name "TestSplashWindow"
then add two windows: "FirstWindow" and "SecondWindow"
each window has a button to open other and then close itself
the differences between them are:
1)FirstWindow opens first and loading time is not matter
2)SecondWindow's loading time takes a while

in App.xaml add Startup
XML
<Application ... Startup="Application_Startup"> ... </Application >

this is App.xaml.cs code:
public partial class App : Application
   {
       public ISplashScreen splashScreen;
       public ManualResetEvent ResetSplash;
       private void Application_Startup(object sender, StartupEventArgs e)
       {
       FirstWindow fw = new FirstWindow();
       fw.Show();
       }
       public void ShowSplashByThread()
       {
       ResetSplash = new ManualResetEvent(false);
       Thread SplashThread = new Thread(ShowSplash);
       SplashThread.SetApartmentState(ApartmentState.STA);
       SplashThread.IsBackground = true;
       SplashThread.Name = "SplashThreadName";
       SplashThread.Start();
       ResetSplash.WaitOne();
       }
       private void ShowSplash()
       {
       LoadingWindow loadingWindow = new LoadingWindow("Profile Name");
       splashScreen = loadingWindow;
       loadingWindow.Show();

       // Now that the window is created, allow the rest of the startup to run
       ResetSplash.Set();

       // WPF automatically creates a new Dispatcher to manage our new thread. All we have to
       // do to make our window functional is ask this Dispatcher to start running
       System.Windows.Threading.Dispatcher.Run();
       }


   }


this is FirstWindow code:
public partial class FirstWindow : Window
    {
       
        public FirstWindow()
        {
            InitializeComponent();  
        }
        
        private void btnOpenSecondWindow_Click(object sender, RoutedEventArgs e)
        {
            ((App)System.Windows.Application.Current).ShowSplashByThread();
            
            SecondWindow sw = new SecondWindow();
            sw.Show();
            this.Close();
        }
    }

this is SecondWindow Code:
public SecondWindow()
     {
         InitializeComponent();
         Thread.Sleep(10000);//presume loading second window takes a while
     }

     private void btnOpenFirstWindow_Click(object sender, RoutedEventArgs e)
     {
         FirstWindow fw = new FirstWindow();
         fw.Show();
         this.Close();
     }

     private void Window_Loaded(object sender, RoutedEventArgs e)
     {
         if (((App)System.Windows.Application.Current).splashScreen != null)
         { ((App)System.Windows.Application.Current).splashScreen.LoadComplete(); }
         ((App)System.Windows.Application.Current).splashScreen = null;
     }


this is splash window code:
   public interface ISplashScreen
    {
        void AddMessage(string message);

        void LoadComplete();
    }
    /// <summary>
    /// Interaction logic for Loading.xaml
    /// </summary>
    public partial class LoadingWindow : Window, ISplashScreen
    {

        public ImageSource LoadingImg
        {
            get { return (ImageSource)GetValue(LoadingImgProperty); }
            set { SetValue(LoadingImgProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Img.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LoadingImgProperty =
            DependencyProperty.Register("LoadingImg", typeof(ImageSource), typeof(LoadingWindow), new PropertyMetadata(null));

        public LoadingWindow(string LabelText)
        {
            LoadingImg = new BitmapImage(new  Uri(System.IO.Path.Combine(Environment.CurrentDirectory,"icon\\Loading20.gif")));

            InitializeComponent();
            lbl.Content = LabelText;
            
        }

        public void AddMessage(string message)
        {
            Dispatcher.Invoke((Action)delegate ()
            {
                this.UpdateMessageTextBox.Text = message;
            });
        }

        public void LoadComplete()
        {
            if (Dispatcher.CheckAccess())
            { this.Close(); }
            else
            { 
                Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(this.Close));
            }
        }

        private void btnClose_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
//it is draggable splash window
        private void LoadingWin_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
                this.DragMove();
        }
    }
Posted
Updated 24-May-23 16:28pm
Comments
Dave Kreskowiak 24-May-23 17:57pm    
It might help if I re-read the question.

You don't have a memory leak. There's nothing in the code that could generate one. You are looking at how much memory is RESERVED for your app, not how much it's actually using.
4L4K1 24-May-23 18:13pm    
I use this code in an application
when I open second windows and close it multiple time
memory usage increases so much (from 50 MB grow ups to 1000 MB)
when I remove splash windows memory do not increase
so i am prety sure this code has memory leak
Dave Kreskowiak 24-May-23 18:49pm    
First, why are you opening and closing a splash screen multiple times? It's usually only ever used once during the lifetime of an app.

Your splash screen doesn't do anything, other than load a .GIF file. I'd probably move the .GIF to resources instead of loading it from a file every time.
4L4K1 25-May-23 13:41pm    
presume an app that user first go to first windows and do not need splash windows
but user can choose to open another windows that takes time
then user can back to first windows and choose another window that is different and need another splash window
Dave Kreskowiak 25-May-23 18:33pm    
That would be the very first time I've ever heard of a splash screen being used more than once in an app in the 40+ years I've been doing this.

1 solution

IF you are only displaying an image as a splash screen, then there is a better way: How to add a splash screen - WPF .NET Framework | Microsoft Learn[^]

However, if you are wanting a more dynamic solution, like displaying loading messages &/or a progress bar, then it is more involved. Here is an example stripped out of a live WPF app.

1. We need a DispatcherHelper class to help with multi-threading:
C#
public static class DispatcherHelper
{
    public static Dispatcher? UIDispatcher { get; private set; }

    public static void CheckBeginInvokeOnUI(Action? action)
    {
        if (action == null)
            return;
        CheckDispatcher();
        if (UIDispatcher != null && UIDispatcher.CheckAccess())
            action();
        else
            UIDispatcher?.BeginInvoke(action);
    }

    private static void CheckDispatcher()
    {
        if (UIDispatcher == null)
        {
            StringBuilder stringBuilder = new("The DispatcherHelper is not initialized.");
            stringBuilder.AppendLine();
            stringBuilder.Append("Call DispatcherHelper.Initialize() in the static App constructor.");
            throw new InvalidOperationException(stringBuilder.ToString());
        }
    }

    public static DispatcherOperation? RunAsync(Action action)
    {
        CheckDispatcher();
        return UIDispatcher?.BeginInvoke(action);
    }

    public static void Initialize()
    {
        if (UIDispatcher != null && UIDispatcher.Thread.IsAlive)
            return;
        UIDispatcher = Dispatcher.CurrentDispatcher;
    }

    public static void Reset() => UIDispatcher = null!;
}

2. Next we need a Splash Window:
XML
<Window x:Class="WpfDynamicSplashScreen.SplashScreenWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"

        Title="SplashScreenWindow"
        Height="100" Width="300" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Text ="SPLASHSCREEN"
                   FontSize="36"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
        <ProgressBar IsIndeterminate="True" Grid.Row="1" Height="10" />
    </Grid>
</Window>

and the code-behind for the DispatcherHelper initialization:
C#
public partial class SplashScreenWindow
{
    public SplashScreenWindow()
    {
        InitializeComponent();
        DispatcherHelper.Initialize();
    }
}

3. Now we can add the bootstrapping code to:
a. Show the Splash Window
b. Do early work needed
c. Show the Main Window
d. Handle the shutting down process

As we are managing the lifecycle of the app, there is some manual work required. The bare minimum is below:
C#
internal class Bootstrap
{
    [STAThread]
    internal static void Main()
    {
        App app = new App { ShutdownMode = ShutdownMode.OnExplicitShutdown };
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());

        Bootstrap bs = new();
        bs.ExitRequested += (sender, e) =>
        {
            // global cleanup goes here
            app.Shutdown();
        };

        Task programStart = bs.StartAsync();
        Task exTask = HandleExceptions(programStart, app);

        app.Run();
    }

    private static async Task HandleExceptions(Task task, Application wpfApplication)
    {
        try
        {
            await Task.Yield(); // ensure this runs as a continuation
            await task;
        }
        catch (Exception ex)
        {
            // deal with exception, either with message box
            // or delegating to general exception handling logic that is wired up 
            // e.g. to app.DispatcherUnhandledException and AppDomain.UnhandledException
            MessageBox.Show(ex.ToString());
            wpfApplication.Shutdown();
        }
    }

    public async Task StartAsync()
    {
        // main window viewmodel
        //var viewModel = ViewModel as IViewModelBootStrap;
        //viewModel.CloseRequested += CloseRequested;

        // hook window closing
        EventHandler windowClosed = (sender, e) => {
            // add handler here ...
            Debug.WriteLine($"{nameof(sender)} requested widow closed");

            // normally handled in the ViewModel of the main Window
            CloseRequested(sender!, e);

        };

        // not disposable, but I'm keeping the same structure
        SplashScreenWindow splashScreen = new SplashScreenWindow();

        splashScreen.Closed += windowClosed; //if user closes splash screen, let's quit
        splashScreen.Show();

        // Main boot sequence where pre-app start work is done...
        await Task.Delay(3000).ConfigureAwait(false); // dummy delay 5000 = 5 seconds

        DispatcherHelper.CheckBeginInvokeOnUI(() =>
        {
            MainWindow mainUI = new MainWindow();
            mainUI.Closed += windowClosed;
            //mainUI.DataContext = mainWindowViewModel;
            mainUI.Show();

            splashScreen.Owner = mainUI;
            splashScreen.Closed -= windowClosed;
            splashScreen.Close();
        });
    }

    public event EventHandler<EventArgs> ExitRequested;
    private void CloseRequested(object sender, EventArgs e)
        => OnExitRequested(EventArgs.Empty);

    protected virtual void OnExitRequested(EventArgs e)
        => ExitRequested(this, e);
}

This solution is designed to work with MVVM, however I have commented out key parts. However, if you are using MVVM, then you will need to use the following interface on the MainWindowViewModel:
C#
// used on MainWindow ViewModel
internal interface IViewModelBootStrap
{
    event EventHandler<EventArgs> CloseRequested;

    void RequestClose();
    Task InitializeAsync();
}

If you are using Mvvm, it would look something like:
C#
public class MainWindowViewModel : ViewModelBase, IViewModelBootStrap
{
    public event EventHandler<EventArgs> CloseRequested;

    public Task InitializeAsync()
    {
        // do any work here
    }

    public void RequestClose()
    {
        // do closing work and raise the
        CloseRequested.Invoke(this, EventArgs.Empty);
    }
}

4. Now, as we are handling the lifecycle of the app, we need to remove the default startup and point to our BootStrap.Main.

First, Remove the MainWindow from App.Xaml:
XML
<Application x:Class="WpfDynamicSplashScreen.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Application.Resources>
         
    </Application.Resources>

</Application>

Now go to the application's Properties (right-click app in solution explorer and choose "Properties") and change "startup" to out BootStrap class.

Now run the app and the Splashscreen will show for 3 seconds with an indeterminate progress bar, then will close and show the MainWindow.

You will have no memory leaks.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900