2012-10-31 46 views
2

我想優化我的WPF棱鏡應用程序的加載時間。加載基本上是一個使用反射的循環來創建UI元素的實例,然後將它們添加到選項卡控件中的主窗口(shell)中。WPF優化加載UI線程

由於我們僅限於使用單個線程創建所有對象,因此加速加載/創建更好的用戶體驗的最佳方式是什麼?

這是我到目前爲止的選項:

  1. 使用延遲加載。只有在用戶第一次點擊時才加載標籤。但是,隨着需求初始化,這將首次開放4-5秒。

  2. 緩存所有反射調用。我其實是這麼做的,但它沒有加速任何事情。大部分時間發生在控件渲染期間...

任何建議將不勝感激這個棘手的問題。

+0

如何加載標籤的內容可能需要4-5秒?它涉及什麼? –

+1

你看過嗎? http://msdn.microsoft.com/en-us/library/aa970683.aspx – CodeNaked

+0

@NicolasRepiquet UI由標籤組成。每個選項卡最多可以有15個子控件:地圖,圖表,表單等。每個子控件都需要反序列化自身,然後計算佈局並控制渲染。這不是一個簡單的應用程序,這些東西可以加起來。 – gchen

回答

0

加速加載的最佳答案是在構建可視化樹時簡單地隱藏容器。

這可以防止屏幕不斷需要自行更新。

當所有元素都添加到可視化樹中時,將容器可見性設置爲可見將呈現標籤容器一次。

我們還對選項卡控件項目實施了一些簡單的延遲渲染。

淨結果:加載時間從2分鐘減少到大約20秒。

+0

加載應用程序20秒仍然聽起來對我來說太多了......我們有一個很大的LOB應用程序,從Entity Framework通過WCF到WPF,服務器端託管在託管中心CA和用戶都在佛羅里達州,加載初始屏幕不超過7或8秒鐘,然​​後加載數據和處理東西在後臺,而不會被最終用戶注意到。 –

0

我會實現一個計時器,每次打勾(迭代)加載一些控件或選項卡。計時器將與UI相同的線程上運行(控制消息將在Windows消息循環中排隊)。一旦所有的工作完成,你可以殺死計時器。

定時器間隔和每個時間片加載的控件數量將歸結爲使用測試;嘗試類似100毫秒和2控制一個滴答,將給你秒〜20控件,所以如果你有10個標籤和15個控件每個需要約8秒,但用戶界面不應該鎖定爲壞。

+0

這是一個有趣的想法。我會試一試,讓你知道這種方法是否有效。我從來沒有這樣做過,所以我不會知道,直到我嘗試。 – gchen

+0

我們在加載過程中隱藏了標籤容器。這是最優雅的解決方案,因爲它阻止了UI不斷自我呈現。 – gchen

2

正如你所說,如果你的對象是DependencyObjects,你就不能使用Multithread。 Kent Boogart discusses this。這就是爲什麼您必須利用INotifyPropertyChanged並執行POCO對象來保存您的數據。這樣你可以多線程獲取數據,然後將這些數據綁定到你的用戶界面。使用DependencyObjects的另一個缺點是您將應用程序綁定到WPF框架太多(DependencyObject是WPF程序集中System.Windows名稱空間中定義的類)(不記得PresentationCore或PresentationFramework))。如果重構不是一個選項,你將不得不考慮像LastCoder提出的解決方案。請注意,你將能夠做很少的多線程(如果有的話),因此你的應用程序不會一直非常敏感。

+0

再次感謝您的洞察力。我們的確有POCO對象用於混洗數據。這些對象在轉換成我們用於綁定的(DependencyObjects)對象之前,其壽命通常很短。你的建議似乎表明我們應該保持這些服務器對象更長的時間。這可能是緩存對象或者一致地獲取它們的好方法。我會嘗試一些事情,並會回覆你。 – gchen

+0

即使實施INotifyPropertyChanged的POCO也沒有幫助我們。我們的poco對象包含需要反序列化的控件,所以我們有相同的線程相關性問題。 – gchen

+0

你的問題是你沒有將UI與業務邏輯/數據分開。您的觀點不應該反序列化或以其他方式從數據中創建。您的觀點應該是設計時在應用程序中定義的純XAML。然後,您可以將此XAML控件綁定到普通對象中的正常CLR屬性,該對象以任何方式從任何線程中獲取。 –

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類在項目屬性/應用程序選項卡我的啓動對象。
我閉上啓動畫面在我OnStartup方法在應用程序結束:

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

這樣快?否 用戶是否認爲速度更快?是的

有時,如果你不能給他們速度,你可以給他們活動。這是一個很好的安慰劑。

+0

+1用於啓動屏幕,使用戶認爲它的加載速度更快。 – Vaccano

+0

@快速解決方案感謝您的建議和示例代碼。我正在使用啓動畫面,但由於UI線程始終處於旋轉狀態,所以它沒有響應。在另一個線程上創建splash窗口的想法是有好處的。我會嘗試一些建議,我會盡快回復您。謝謝! – gchen

+0

這裏要記住的重要一點是我們正在運行2個Dispatchers,所以當你的應用程序準備好時,你可以在splash窗口中做一些「漂亮」的事情。在我的情況下,我使用混合來取得公司的圖標,並用它做一些摺紙。 –