WCF 提供三種經典的服務實例化模式,分別對應 InstanceContextMode 的三種設定:PerCall、PerSession 與 Single。其中 PerCall 每次呼叫都建立新實例,最符合 SOA 無狀態服務的設計原則;PerSession 維持同一 Proxy 的連線狀態;Single 則共用單一實例處理所有請求。雖然 PerCall 是最推薦的模式,但頻繁建立與銷毀物件在高併發下會造成效能壓力。有沒有辦法兼顧無狀態優勢與物件重用效能?答案是引入物件池機制。
我們的目標是:請求到達時,優先從池中取得可用實例(IsBusy = false);若無可用則建立新實例;執行完畢後歸還池中而非銷毀。為了避免物件長期佔用記憶體,採用 WeakReference 讓 GC 能自然回收閒置實例,並由背景任務定期清理已失效的弱引用。
實作關鍵在於自訂 IInstanceProvider,這與本系列前兩篇整合 Unity 與 Policy Injection 的思路一脈相承。
所有服務類別需實作此介面,標示目前是否忙碌:
namespace Artech.WCFExtensions
{
public interface IPooledObject
{
bool IsBusy { get; set; }
}
}
namespace Artech.WCFExtensions
{
public class WeakReferenceCollection : List<WeakReference> { }
public class WeakReferenceDictionary : Dictionary<Type, WeakReferenceCollection> { }
}
以服務型別為 Key,對應一組弱引用集合作為該型別的物件池。
靜態類別封裝三大核心操作:
IsBusy == false 的實例,找到則標記忙碌並回傳;否則透過反射建立新實例、包裝成弱引用加入池中。IsBusy 設為 false。Target == null(已被 GC 回收)的弱引用。程式碼採用 lock(serviceType) 保證執行緒安全,實務上建議改用專用同步物件避免 Type 物件鎖定風險。
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);
}
}
}
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。
namespace Contracts
{
[ServiceContract]
[PooledInstanceBehavior] // 套用自訂 Behavior
public interface IService : IPooledObject
{
[OperationContract(IsOneWay = true)]
void DoSomething();
}
}
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; }
}
}
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();
}
}
}
}
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();
}
}
}
透過 IInstanceProvider 搭配 WeakReference 物件池,我們成功在保持 PerCall 無狀態語意的前提下,大幅降低高併發下的物件建立開銷,並善用 GC 機制避免記憶體洩漏。此模式適用於實例化成本較高(如需載入大量設定、建立昂貴連線)的服務場景。