WPF优化UI线程上的加载

3
我正在尝试优化我的WPF Prism应用程序的加载时间。加载过程基本上是使用反射创建UI元素实例并将它们添加到主窗口(Shell)中的选项卡控件。

由于我们只能使用单个线程来创建所有对象,因此有什么最佳方法可以加速加载/创建更好的用户体验呢?

以下是我目前拥有的选项:

  1. 使用延迟加载。仅在用户首次点击选项卡时加载该选项卡。但这会导致第一次打开时需要4-5秒的延迟,因为它是按需初始化的。

  2. 缓存所有反射调用。我实际上已经这样做了,但没有提高任何速度。大多数时间都用于控件的渲染...

非常感谢对这个棘手问题的任何建议。


1
你看过这个吗?http://msdn.microsoft.com/zh-cn/library/aa970683.aspx - CodeNaked
@NicolasRepiquet 用户界面由选项卡组成。每个选项卡最多可以有15个子控件:地图、图表、表单等。每个子控件需要反序列化自身,然后计算布局并呈现控件。这不是一个简单的应用程序,这些操作会累加起来。 - gchen
1
@HighCore,“状态”在维基百科中的定义是:“在计算机科学和自动机理论中,数字逻辑电路或计算机程序的状态是一个技术术语,指在某一时刻存储的所有信息,这些信息被电路或程序使用。” 这就是我的意思。不需要示例代码。 - gchen
1
@HighCore 不正确。要参与数据绑定,对象必须从 DependencyObject 继承。DependencyObject 必须在与窗口所有者相同的线程上创建。正确的方法是创建封装仅数据而不从 DependencyObject 继承的包装器对象。这种方法太繁琐了,因为每个可绑定类都需要 1:1 的 DTO。 - gchen
1
@HighCore 感谢您的评论。我们已经考虑了 INotifyPropertyChanged。我正在寻找创意/优雅的解决方案。如果没有,我可以接受这一点。 - gchen
显示剩余9条评论
4个回答

3
您基本上无法摆脱,因为您只能在主线程上加载对象,所以我认为您不会使其加载更快。

您可以做的是分散用户的注意力:我有一个动画闪屏,需要约10秒钟才能完成动画序列。这有多种用途:

  1. 它向用户展示了运动-因此他们有一个视觉提示,表示正在进行某些操作
  2. 它分散了他们的注意力,并填充了初始加载所占用的空间

为确保平滑的动画效果,您需要创建第二个调度程序。以下是我的做法:

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);
        }
    }

我在项目属性/应用程序选项卡中将AppEntry类设置为启动对象。
在App的OnStartup方法结束时关闭启动画面:

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

这样做更快吗? 不是的。 用户认为它更快吗? 是的。

有时,如果你无法提供速度,你可以让他们感到有所活动。这是一种不错的安慰剂。


+1 是为了制作欢迎屏幕,让用户觉得它加载更快。 - Vaccano
@Faster Solutions 谢谢你的建议和示例代码。我正在使用启动画面,但由于UI线程一直在旋转,它并不是很响应。在另一个线程上创建启动窗口的想法很有价值。我将尝试一些建议,然后再与你联系。谢谢! - gchen
在这里需要记住的重要事情是我们正在运行2个调度程序,因此当您的应用程序准备就绪时,您可以在启动窗口中做一些“漂亮”的事情。在我的案例中,我使用混合工具获取了公司图标并对其进行了一些折纸处理。 - Faster Solutions
如何授予部分学分?我也想给你的回答一些学分。 - gchen

2
正如您所提到的,如果您的对象是DependencyObjects,则无法进行多线程操作。Kent Boogart在这里讨论了这个问题。这就是为什么您必须利用INotifyPropertyChanged并使用POCO对象来保存您的数据。这样,您就可以使用多线程获取数据,然后将其绑定到UI上。使用DependencyObjects的另一个缺点是,您将应用程序过于依赖WPF框架(DependencyObject是在WPF程序集中System.Windows命名空间下定义的类(我不记得是PresentationCore还是PresentationFramework))。如果重构不是选项,您将不得不考虑像LastCoder提出的解决方案。请注意,您将能够做很少的多线程操作(如果有的话),因此您的应用程序在所有时间都不会非常响应。

再次感谢您的见解。我们确实有用于传输数据的POCO对象。这些对象在转换为(DependencyObjects)绑定对象之前往往寿命很短。您的建议似乎表明我们应该将这些服务器对象保留更长时间。这可能是缓存对象或同时获取它们的好方法。我会尝试一些东西并回复您。 - gchen
即使实现了INotifyPropertyChanged的POCO也无法帮助我们。我们的POCO对象包含需要反序列化的控件,因此我们仍然面临着相同的线程亲和性问题。 - gchen
你的问题在于没有将 UI 和业务逻辑/数据分离开来。你的视图不应该从数据中反序列化或以其他方式创建。你的视图应该是在应用程序的设计时纯 XAML 定义的。然后,你可以将这些 XAML 控件绑定到正常的 CLR 属性上,这些属性可以通过任何方式从任何线程中获取到正常的对象。 - Federico Berasategui
控件不应该被包含在应用程序的视觉层以外的任何东西中,完全解耦数据层,并且只通过绑定引用数据。 - Federico Berasategui
这就是WPF和MVVM与Winforms和其他我所知道的UI框架不同的地方,你可以(而且应该)以真正的方式将UI与应用逻辑和数据分离。 - Federico Berasategui
显示剩余2条评论

1
我会实现一个定时器,在每次间隔(tick)中加载一些控件或选项卡。定时器将在与 UI 相同的线程上运行(用于它的控制消息将排队等候 Windows 消息循环)。一旦所有工作完成,您可以杀死定时器。
定时器间隔和每个间隔加载的控件数量将归结于用户测试。尝试设置类似于 100ms 和每个间隔加载 2 个控件,这将为您提供每秒约 20 个控件,因此如果您有 10 个带有 15 个控件的选项卡,那么需要大约 8 秒钟,但 UI 不应该锁定得那么厉害。

这是一个有趣的想法。我将尝试一下并告诉你是否此方法似乎有效。我以前从未尝试过,所以在尝试之前我不会知道结果。 - gchen
我们最终在加载期间隐藏了选项卡容器。这是最优雅的解决方案,因为它防止了用户界面不断地重新渲染。 - gchen

1
加快加载速度的最佳方法是在构建可视树时隐藏容器。
这可以防止屏幕不断地需要更新自己。
当所有元素都添加到可视树中后,将容器可见性设置为可见,一次渲染选项卡容器。
我们还实现了一些简单的延迟渲染到选项卡控件项。
净结果:从2分钟的加载时间缩短到约20秒。

20秒的应用程序加载时间对我来说仍然太长了...我们有一个大型LOB应用程序,从Entity Framework通过WCF到WPF,服务器端托管在加利福尼亚州的托管中心,用户位于佛罗里达州,它不需要超过7或8秒钟即可加载初始屏幕,然后在后台继续加载数据和处理内容,而不会被最终用户注意到。 - Federico Berasategui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接