首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 進階實戰:透過擴展實現 Service Instance 物件池機制

     編輯:關於.NET

前言:WCF 實例化模式的權衡

WCF 提供三種經典的服務實例化模式,分別對應 InstanceContextMode 的三種設定:PerCall、PerSession 與 Single。其中 PerCall 每次呼叫都建立新實例,最符合 SOA 無狀態服務的設計原則;PerSession 維持同一 Proxy 的連線狀態;Single 則共用單一實例處理所有請求。雖然 PerCall 是最推薦的模式,但頻繁建立與銷毀物件在高併發下會造成效能壓力。有沒有辦法兼顧無狀態優勢與物件重用效能?答案是引入物件池機制。

核心設計思路

我們的目標是:請求到達時,優先從池中取得可用實例(IsBusy = false);若無可用則建立新實例;執行完畢後歸還池中而非銷毀。為了避免物件長期佔用記憶體,採用 WeakReference 讓 GC 能自然回收閒置實例,並由背景任務定期清理已失效的弱引用。

實作關鍵在於自訂 IInstanceProvider,這與本系列前兩篇整合 Unity 與 Policy Injection 的思路一脈相承。

關鍵元件實作

1. IPooledObject 介面

所有服務類別需實作此介面,標示目前是否忙碌:

namespace Artech.WCFExtensions
{
    public interface IPooledObject
    {
        bool IsBusy { get; set; }
    }
}

2. 弱引用集合與字典

namespace Artech.WCFExtensions
{
    public class WeakReferenceCollection : List<WeakReference> { }
    public class WeakReferenceDictionary : Dictionary<Type, WeakReferenceCollection> { }
}

以服務型別為 Key,對應一組弱引用集合作為該型別的物件池。

3. PooledInstanceLocator 核心邏輯

靜態類別封裝三大核心操作:

  • GetInstanceFromPool:遍歷弱引用尋找 IsBusy == false 的實例,找到則標記忙碌並回傳;否則透過反射建立新實例、包裝成弱引用加入池中。
  • ReleaseInstanceToPool:單純將 IsBusy 設為 false。
  • Scavenge:定期掃描並移除 Target == null(已被 GC 回收)的弱引用。

程式碼採用 lock(serviceType) 保證執行緒安全,實務上建議改用專用同步物件避免 Type 物件鎖定風險。

4. PooledInstanceProvider

namespace Artech.WCFExtensions
{
    class PooledInstanceProvider : IInstanceProvider
    {
        public object GetInstance(InstanceContext instanceContext, Message message)
        {
            return PooledInstanceLocator.GetInstanceFromPool(
                instanceContext.Host.Description.ServiceType);
        }

        public object GetInstance(InstanceContext instanceContext)
            => GetInstance(instanceContext, null);

        public void ReleaseInstance(InstanceContext instanceContext, object instance)
        {
            PooledInstanceLocator.ReleaseInstanceToPool(instance as IPooledObject);
        }
    }
}

5. PooledInstanceBehaviorAttribute (Contract Behavior)

namespace Artech.WCFExtensions
{
    public class PooledInstanceBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
    {
        public void ApplyDispatchBehavior(ContractDescription contractDescription,
            ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            dispatchRuntime.InstanceProvider = new PooledInstanceProvider();
        }
        // 其他介面成員略...
        public Type TargetContract => null;
    }
}

實戰驗證

採用經典四層架構:Contract、Service、Hosting、Client。

Contract 端

namespace Contracts
{
    [ServiceContract]
    [PooledInstanceBehavior]  // 套用自訂 Behavior
    public interface IService : IPooledObject
    {
        [OperationContract(IsOneWay = true)]
        void DoSomething();
    }
}

Service 端

namespace Services
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Service : IService
    {
        static int count;
        public Service()
        {
            Interlocked.Increment(ref count);
            Console.WriteLine($"{count}: Service instance is constructed!");
        }

        public void DoSomething() { }

        public bool IsBusy { get; set; }
    }
}

Hosting 端(含定時清理)

namespace Hosting
{
    class Program
    {
        static Timer ScavengingTimer;
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(Service)))
            {
                host.Opened += delegate { Console.WriteLine("Service started!"); };
                host.Open();

                ScavengingTimer = new Timer(delegate
                {
                    PooledInstanceLocator.Scavenge();
                }, null, 0, 5000);

                Console.Read();
            }
        }
    }
}

Client 端測試

namespace Clients
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ChannelFactory<IService> factory = new ChannelFactory<IService>("service"))
            {
                for (int i = 1; i <= 10; i++)
                {
                    Console.WriteLine($"{i}: Invoke service!");
                    factory.CreateChannel().DoSomething();
                    Thread.Sleep(1000);
                }
            }
            Console.Read();
        }
    }
}

測試結果分析

  • 啟用物件池:10 次呼叫僅建立 1 次 實例(方法執行極快,實例即時歸還重用)。
  • 停用物件池:真正呈現 PerCall 行為,建立 10 個實例。
  • 模擬長時間執行 (2 秒):Client 每秒呼叫一次,導致前兩次需建立新實例,第三次起重用第一個,全程僅建立 2 個 實例。
  • 引入 GC 壓力測試:縮短執行時間 (500ms) 並每 500ms 強制 GC,結果又回到每次皆建立新實例,證明弱引用機制讓閒置實例被正確回收。

總結

透過 IInstanceProvider 搭配 WeakReference 物件池,我們成功在保持 PerCall 無狀態語意的前提下,大幅降低高併發下的物件建立開銷,並善用 GC 機制避免記憶體洩漏。此模式適用於實例化成本較高(如需載入大量設定、建立昂貴連線)的服務場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved