简体   繁体   中英

WPF optimize loading on the UI thread

I am trying to optimize the loading times for my WPF prism application. The loading is basically a loop of using reflection to create instances of UI elements and then adding them to the main window (the shell) in a tab control.

Since we are limited to using a single thread to create all the objects, what would be the optimal way to speed up loading / create a better user experience?

These are the options I have so far:

  1. Use lazy loading. Only load the tab when the user first clicks on it. But that would have a 4-5 second delay opening the first time as it gets initialized on demand.

  2. Cache all the reflection calls. I actually did that, but it did not speed anything up at all. Most of the time occurs during the rendering of the controls...

  3. ?

Any suggestions would be greatly appreciated for this tricky problem.

You're pretty much stuck as you can only load objects on the main thread, so I don't think you'll make it load any faster.

What you can do is distract the user: I have an animated splash screen that take about 10 seconds to work its way through the animation sequence. This serves a number of purposes:

  1. It shows the user motion - so they have a visual cue that something is going on
  2. It distracts them and fills the space taken by the initial load

To ensure smooth animation you need to create a second dispatcher. Here's how I do it:

public class AppEntry : Application
    {
        private static ManualResetEvent _resetSplashCreated;

        internal static Thread SplashThread { get; set; }

        internal static SplashWindow SplashWindow { get; set; }

        private static void ShowSplash()
        {
            SplashWindow = new SplashWindow();
            SplashWindow.Show();
            _resetSplashCreated.Set();
            Dispatcher.Run();
        }

        [STAThread]
        public static void Main()
        {
            _resetSplashCreated = new ManualResetEvent(false);
            SplashThread = new Thread(ShowSplash);
            SplashThread.SetApartmentState(ApartmentState.STA);
            SplashThread.IsBackground = true;
            SplashThread.Name = "Splash Screen";
            SplashThread.Start();

            _resetSplashCreated.WaitOne();

            var app = new App();
            app.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(app_DispatcherUnhandledException);
            app.InitializeComponent();
            app.Run();

        }

        static void app_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
           // MessageBox.Show(e.Exception.StackTrace);
        }
    }

I set the AppEntry class as my Startup Object in the Project Properties/Application tab.
I close my splash screen at the end of my OnStartup method in App:

 AppEntry.SplashWindow.Dispatcher.BeginInvoke(DispatcherPriority.Background,
                                                             new Action(() => AppEntry.SplashWindow.Close()));

Is this faster? No Does the user think it's faster? Yes

Sometimes, if you can't give them speed, you can give them activity. It's a nice placebo.

As you mentioned, You cannot Multithread if your objects are DependencyObjects. Kent Boogart discusses this . That's why you must leverage INotifyPropertyChanged and do POCO objects to hold your data. That way you can multithread to obtain the data and then bind these to your UI. Another drawback of using DependencyObjects is that you're tying your application too much to the WPF framework (DependencyObject being a class defined in the System.Windows namespace in a WPF assembly (don't remember if PresentationCore or PresentationFramework)). If refactoring is not an option, you will have to consider a solution like the one LastCoder proposed. Be aware that you will be able to do very little multithreading (if any at all), therefore your application is not going to be very responsive all the time.

I would implement a timer that loads a few controls or tabs every tick (iteration). The timer will run on the same thread as the UI (control messages for it will be queued up on the Windows Message Loop). Once all of the work is done you can kill the timer.

The timer interval and the number of controls to load per tick will boil down to use-testing; try something like 100ms and 2 controls a tick that will give you ~20 controls a second, so if you had 10 tabs with 15 controls each it would take ~8seconds, but the UI shouldn't lock up as bad.

The best answer to speeding up the loading is to simply hide the container while the visual tree is being constructed.

This prevents the screen from constantly needing to update itself.

When all the elements have been added to the visual tree, then setting the container visibility to visible renders the tab container once.

We also implemented some simple lazy-rendering to the tab control items.

Net result: loading times from 2 minutes down to about 20 seconds.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM