共用方式為


Pooling

共用範例會示範如何擴充 Windows Communication Foundation (WCF) 以支援物件共用。 此範例會示範如何建立語法上及語意上與 Enterprise Services 的 ObjectPoolingAttribute 屬性功能相似的屬性。 物件共用可以大幅提升應用程式的效能。 不過,如果不當使用,可能會產生反效果。 物件共用有助於避免重複建立常用物件的煩瑣工作,這些物件往往需要大量的初始設定。 不過,如果呼叫共用物件上的方法要花費相當長的時間才能完成,而一旦到達集區的大小上限,物件共用就得將多出的要求加入佇列中。 它可能因此無法受理某個建立物件的要求,而擲回逾時例外狀況。

注意

此範例的安裝程序與建置指示位於本主題的結尾。

建立 WCF 擴充的第一個步驟是決定要使用的擴充點。

在 WCF 中,「發送器」這個詞彙是指執行階段元件,這個元件會負責將傳入訊息轉換為使用者服務上的方法引動過程,並且將來自該方法的傳回值轉換為傳出訊息。 WCF 服務會為每個端點建立發送器。 如果與這個用戶端關聯的合約是雙工合約,WCF 用戶端如果必須使用發送器。

通道和端點發送器可以公開各種控制發送器行為的屬性,提供適用於整個通道及合約範圍的擴充性。 DispatchRuntime 屬性同時可讓您檢查、修改或自訂分派程序。 此範例著重於 InstanceProvider 屬性,這個屬性會指向提供服務類別之執行個體的物件。

IInstanceProvider

在 WCF 中,發送器藉由使用會實作 IInstanceProvider 介面的 InstanceProvider,來建立服務類別的執行個體。 這個介面有三個方法:

物件集區

自訂的 IInstanceProvider 實作會為服務提供必要的物件共用語意 (Semantics)。 因此,這個範例具有 ObjectPoolingInstanceProvider 型別,可以提供進行共用所需之 IInstanceProvider 的自訂實作。 當 Dispatcher 呼叫 GetInstance(InstanceContext, Message) 方法,而不是建立新執行個體時,自訂實作會在記憶體中集區尋找現有物件。 如果找到了,就會將物件傳回。 否則,會建立新的物件。 下列範例程式碼會示範 GetInstance 實作。

object IInstanceProvider.GetInstance(InstanceContext instanceContext, Message message)
{
    object obj = null;

    lock (poolLock)
    {
        if (pool.Count > 0)
        {
            obj = pool.Pop();
        }
        else
        {
            obj = CreateNewPoolObject();
        }
        activeObjectsCount++;
    }

    WritePoolMessage(ResourceHelper.GetString("MsgNewObject"));

    idleTimer.Stop();

    return obj;
}

自訂 ReleaseInstance 實作會將釋放的執行個體新增回集區,並遞減 ActiveObjectsCount 值。 Dispatcher 可以從不同執行緒中呼叫這些方法,因此需要可在 ObjectPoolingInstanceProvider 類別中同步存取類別層級成員的權限。

void IInstanceProvider.ReleaseInstance(InstanceContext instanceContext, object instance)
{
    lock (poolLock)
    {
        pool.Push(instance);
        activeObjectsCount--;

        WritePoolMessage(
        ResourceHelper.GetString("MsgObjectPooled"));

        // When the service goes completely idle (no requests
        // are being processed), the idle timer is started
        if (activeObjectsCount == 0)
            idleTimer.Start();
    }
}

ReleaseInstance 方法會提供「清除初始化」功能。 集區通常會在集區的存留期中保留最低限度數目的物件。 不過,可能有些時段會出現過度使用的情形,因而需要在集區中建立其他物件以達到組態中指定的上限。 最後當集區的活動變少時,這些多餘的物件可能會變成額外的負荷。 因此,當 activeObjectsCount 成為零時,閒置計時器就會啟動,以觸發並執行清除循環。

新增行為

您可以使用下列行為來連結發送器層延伸:

  • 服務行為。 這些行為允許自訂整個服務執行階段。

  • 端點行為。 這些行為允許自訂服務端點,也就是通道和端點發送器。

  • 合約行為。 這些行為允許分別在用戶端和服務上自訂 ClientRuntimeDispatchRuntime 類別。

若要做為物件共用延伸之用,您必須建立服務行為。 服務行為是藉由實作 IServiceBehavior 介面來建立。 有一些方法可以讓服務模型察覺自訂行為:

  • 使用自訂屬性。

  • 以命令方式將它加入至服務描述的行為集合。

  • 延伸組態檔。

這個範例會使用自訂屬性。 建構 ServiceHost 時,會檢查服務型別定義中使用的屬性,並將可用的行為加入至服務描述的行為集合。

IServiceBehavior 介面中有三個方法:ValidateAddBindingParametersApplyDispatchBehaviorValidate 方法是用來確保行為可以套用至服務。 在這個範例中,此實作會確認服務不是透過 Single 所設定。 AddBindingParameters 方法是用來設定服務的繫結。 這在本案例中不是必要項。 ApplyDispatchBehavior 是用來設定服務的發送器。 WCF 會在初始化 ServiceHost 時呼叫這個方法。 傳遞至這個方法中的參數如下:

  • Description:這個引數會為整個服務提供服務描述。 這可以用來檢查有關服務之端點、合約、繫結程序及其他資料的描述資料。

  • ServiceHostBase:這個引數會提供目前在初始化中的 ServiceHostBase

在自訂 IServiceBehavior 實作中,會產生 ObjectPoolingInstanceProvider 的新執行個體,然後將它指派給 ServiceHostBase 中每個 InstanceProviderDispatchRuntime 屬性。

void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
    // Create an instance of the ObjectPoolInstanceProvider.
    ObjectPoolingInstanceProvider instanceProvider = new
           ObjectPoolingInstanceProvider(description.ServiceType,
                                                    minPoolSize);

    // Forward the call if we created a ServiceThrottlingBehavior.
    if (this.throttlingBehavior != null)
    {
        ((IServiceBehavior)this.throttlingBehavior).ApplyDispatchBehavior(description, serviceHostBase);
    }

    // In case there was already a ServiceThrottlingBehavior
    // (this.throttlingBehavior==null), it should have initialized
    // a single ServiceThrottle on all ChannelDispatchers.
    // As we loop through the ChannelDispatchers, we verify that
    // and modify the ServiceThrottle to guard MaxPoolSize.
    ServiceThrottle throttle = null;

    foreach (ChannelDispatcherBase cdb in
            serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;
        if (cd != null)
        {
            // Make sure there is exactly one throttle used by all
            // endpoints. If there were others, we could not enforce
            // MaxPoolSize.
            if ((this.throttlingBehavior == null) &&
                        (this.maxPoolSize != Int32.MaxValue))
            {
                throttle ??= cd.ServiceThrottle;
                if (cd.ServiceThrottle == null)
                {
                    throw new
InvalidOperationException(ResourceHelper.GetString("ExNullThrottle"));
                }
                if (throttle != cd.ServiceThrottle)
                {
                    throw new InvalidOperationException(ResourceHelper.GetString("ExDifferentThrottle"));
                }
             }

             foreach (EndpointDispatcher ed in cd.Endpoints)
             {
                 // Assign it to DispatchBehavior in each endpoint.
                 ed.DispatchRuntime.InstanceProvider =
                                      instanceProvider;
             }
         }
     }

     // Set the MaxConcurrentInstances to limit the number of items
     // that will ever be requested from the pool.
     if ((throttle != null) && (throttle.MaxConcurrentInstances >
                                      this.maxPoolSize))
     {
         throttle.MaxConcurrentInstances = this.maxPoolSize;
     }
}

除了 IServiceBehavior 實作之外,ObjectPoolingAttribute 類別還有數個可以使用屬性引數來自訂物件集區的成員。 這些成員包括 MaxPoolSizeMinPoolSizeCreationTimeout,可以用來配合 .NET Enterprise Services 提供的物件共用功能集。

現在,使用新建立的自訂 ObjectPooling 屬性對服務實作加上附註,即可將物件共用行為加入至 WCF 服務。

[ObjectPooling(MaxPoolSize=1024, MinPoolSize=10, CreationTimeout=30000)]
public class PoolService : IPoolService
{
  // …
}

執行範例

此範例會示範可在特定案例中藉由使用物件共用取得的效能優勢。

服務應用程式會實作兩個服務:WorkServiceObjectPooledWorkService。 這兩個服務會共用相同的實作;它們都必須進行昂貴的初始設定,然後再公開相對低廉的 DoWork() 方法。 唯一的差異在於 ObjectPooledWorkService 設定了物件共用:

[ObjectPooling(MinPoolSize = 0, MaxPoolSize = 5)]
public class ObjectPooledWorkService : IDoWork
{
    public ObjectPooledWorkService()
    {
        Thread.Sleep(5000);
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService instance created.");
    }

    public void DoWork()
    {
        ColorConsole.WriteLine(ConsoleColor.Blue, "ObjectPooledWorkService.GetData() completed.");
    }
}

當您執行用戶端時,它會計算呼叫 WorkService 5 次的時間, 接著計算呼叫 ObjectPooledWorkService 5 次的時間, 然後顯示時間的差距:

Press <ENTER> to start the client.

Calling WorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling WorkService took: 26722 ms.
Calling ObjectPooledWorkService:
1 - DoWork() Done
2 - DoWork() Done
3 - DoWork() Done
4 - DoWork() Done
5 - DoWork() Done
Calling ObjectPooledWorkService took: 5323 ms.
Press <ENTER> to exit.

注意

第一次執行用戶端時,兩個服務似乎都花費了相同的時間。 如果您重新執行範例,就可以看出 ObjectPooledWorkService 的回傳速度快多了,這是因為此物件的執行個體已經存在於集區中。

若要安裝、建置及執行範例

  1. 確定您已執行 Windows Communication Foundation 範例的一次性安裝程序

  2. 若要建置解決方案,請依照建置 Windows Communication Foundation 範例中的指示操作。

  3. 若要在單一或多部電腦組態中執行此範例,請遵循執行 Windows Communication Foundation 範例中的指示進行。

注意

如果您使用 Svcutil.exe 重新產生這個範例的組態,請務必修改用戶端組態中的端點名稱,以符合用戶端程式碼。