程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 性能優化總結(四):預加載的設計

性能優化總結(四):預加載的設計

編輯:關於.NET

本節說一下數據的預加載。這節的內容與SQL沒什麼關系。主要說的是在 GIX4項目 中,我們是如何設計符合需求的預加載類庫的。內 容如下:

1. 什麼是預加載,為什麼要用它?

2. 我們所需要的API

3. 一個簡單的例子

什麼是預加載?

預加載其實就是在真正開始使用數據之前,先異步把數據加載好,等到需要使用時,就可以直接使用之前加載好的數據。這時,由於數 據已經加載完成,而不用等待漫長的加載過程,所以程序的速度得到一個明顯的提升。

那麼,什麼時候需要使用它呢?我覺得,主要是這種情況:當我們可以預知程序接下來的步驟中,很可能會用到一些數據,而獲取這些 數據的操作比較耗時的時候,我們就可以使用預加載的方式,提前把數據准備好。

預加載需要使用異步方法,也就是使用後台線程來加載數據。這樣做的好處當然是不會阻塞當前的主線程。(不過如果當前線程本身就 是用於異步加載數據的話,那就沒必要再新開線程了。)

我們可以使用很多種方式來實現異步加載:在.NET Framework的類庫中,很多地方都提供了異步編程模式(Asynchronous Programming Design Patterns)的API,使用這個模式,可以方便地實現各種異步加載。當然,我們也可以使用2.0提供的 ThreadPool.QueueUserWorkItem來實現一些輕量級的異步操作。在.NET4.0最新的API中,提供了Task類來表示可執行任務。

但是,這些並不是我想要的API……

我們所需要的API

目前系統中預加載使用的場景需求是這樣的:

1. 預加載可以對指定的數據獲取操作(loading action)進行封裝,在需要時調用。

2. 使用數據的模塊(使用者),並不一定知道是誰、在何時給它提前加載的數據。它只會申請使用數據。

3. 發起異步加載的模塊(發起者),應該知道使用者是誰。

4. 多個發起者之間沒有關系,但是都可以為某一使用者發起預加載。但是保證真正的數據加載操作,只會發生一次。

5. 支持重新加載。

6. 一個類中,支持對它不同的數據進行不同加載方式,以方便按需加載。

7. 從使用者的角度來看,不管有沒有發起者為它進行預加載,它都可以申請並拿到想要的數據。也就是說:

當沒有發起者為它進行預加載,那麼它的數據申請會導致即時的數據加載;

如果已經發起了預加載,而且數據已經加載完成,則直接獲取到加載好的數據;

如果數據沒有完成,則數據使用者需要等待數據的加載完成後,才可以獲取到數據並繼續當前的操作。

其中,最重要的就是最後點。

可以看到,這裡需要用到異步操作、線程間同步。所以我們需要基於上面提到的多種API來實現,這裡我們使用的是簡單的線程池的方 式,比較簡單,不再贅述。

最後設計出的API大致是這樣的:

namespace OpenExpressApp
{
     public enum LoaderStatus
     {
         NotStarted = 0,
         Running = 1,
         Completed = 2,
         Failed = 3,
     }
     public class ForeAsyncLoader
     {
         public ForeAsyncLoader(Action loadAction);
         public LoaderStatus Status { get; }
         public event EventHandler ActionSucceeded;
         /// <summary>
         /// 申請啟用線程進行預加載。
          /// 注意:
          /// 本方法可以重入,多次調用也只會執行一次ladAction
          /// </summary>
         public void BeginLoading();
         /// <summary>
         /// 重設加載器。
          /// 使用此方法後,再次申請預加載時,會再次執行loadAction。
         /// </summary>
         public void Reset();
         /// <summary>
         /// 等待數據加載完成。
          /// </summary>
         public void WaitForLoading();
     }
}

例子

客戶程序使用時,需要為其定義一個屬性,舉例如下:

數據持有者:

public class DataHolder
{
     private object _data;
     public object Data
     {
         get
         {
             return this._data;
         }
     }
     private object GetDataFromWeb()
     {
         //...
     }
}

如果它的data1數據加載比較慢,我們可以為其定義一個預加載屬性:

private ForeAsyncLoader _dataLoader;
/// <summary>
/// 數據加載器
/// </summary>
public ForeAsyncLoader DataLoader
{
     get
     {
         if (this._dataLoader == null)
         {
             this._dataLoader = new ForeAsyncLoader(() =>
             {
                 //真正加載數據
                 this._data = this.GetDataFromWeb();
             });
         }
         return this._dataLoader;
     }
}

這樣,數據的“消費者”就可以使用這個數據:

public class DataConsumer
{
     private void Process(DataHolder holder)
     {
         holder.DataLoader.WaitForLoading();
         var data = holder.Data;
         //consume data...
     }
}

在這裡,雖然使用者並不知道有沒有其它代碼給holder執行了數據的預加載,但是當WaitForLoading方法執行完成後,數據是必然獲取 到本地了。所以就可以直接使用數據。我們甚至可以把這句代碼放在Data屬性的get代碼塊中,這樣,使用者甚至都不知道數據的獲取方案 !

然後,可以在運行於它之前的代碼中,為這個“DataHolder”申請預加載。例如,我們在應用程序啟動的時候,就開始預加載。下面的 方法調用了BeginLoading方法,此方法會使用後台線程加載數據,所以這裡會立即返回:

public class Invoker
{
     private DataHolder _holder = new DataHolder();
     private void App_Start()
     {
         this._holder.DataLoader.BeginLoading();
         //do other things
     }
}

至此,就完成了一個最簡單的預加載。

過程如下:

(圖畫得不熟,哪畫錯了,望大家指正,謝謝。)

小結 本篇主要說了一下在目前的系統中,如何設計出一個滿足場景應用需求的預加載API。

預加載是一個經常會被使用到的模式,希望對大家有用。

下一篇我會寫一下與目前系統關聯比較大的內容:與GIX4對象模型相關的“預加載、延遲加載、聚合SQL的組合應用”;另外可能會順 便說一下,如何讓CSLA的服務端框架支持多線程並發。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved