My application has a child container per request, this is because there are a lot of services used in each request which depend upon an IUnitOfWork. I thought it would be nice if I could define a pool of these IUnitOfWork instances so that any cost involved in creating them is reduced, they can just be reused in a round-robin fashion. Well, more accurately, the object space (EcoSpace) on which they depend can anyway.
A pool can now be registered like so…
1 //Must be called once, when the container is created
2 container.AddNewExtension<PooledLifetimeExtension>();
3 //To register a pooled item
4 container.RegisterType
5 <
6 ISomeItemThatIsExpensiveToCreate,
7 SomeItemThatIsExpensiveToCreate
8 >(new PooledLifetimeManager(10));
The number 10 specifies how many instances at once may be stored in the pool. If an instance requests an item from the pool when it is empty it will get a new instance, it’s only at the point where an instance is returned to the pool that the MaxPoolSize is honoured – if the pool is not full then the instance goes back into the pool, if the pool is already full then Dispose is called on it instead. Of course you should be careful when pooling that your pooled items don’t hold onto other injected references which are also pooled as that will cause problems.
I have also added the following interface so that an instance can (optionally) specify whether or not its current state should permit it to go back into the pool, and two methods which indicate to the instance when it has been retrieved from a pool or is about to be returned to the pool – in my case I deactivated the object space upon returning in order to flush any object cache, and reactivated it again when retrieved from the pool.
1 public interface IPooledResource
2 {
3 bool CanReturnToPool { get; }
4 void RetrievedFromPool();
5 void ReturningToPool();
6 }
It’s important to note that this only works with child containers. You should create your main controller as a kind of template by registering your request specific services (like IUnitOfWork) with HierarchicalLifetimeManager. The PooledLifetimeManager will work in the same way as HierarchicalLifetimeManager except for the addition of pooling, and it will automatically attempt to return items to the pool once the child container is disposed of.
1 //PooledLifetimeExtension.cs
2 using Microsoft.Practices.Unity;
3 using Microsoft.Practices.Unity.ObjectBuilder;
4
5 namespace YourNameHere.Infrastructure.Unity
6 {
7 public class PooledLifetimeExtension : UnityContainerExtension
8 {
9 protected override void Initialize()
10 {
11 // Add to type mapping stage so that it runs before the lifetime stage
12 Context.Strategies.AddNew<PooledLifetimeStrategy>
13 (UnityBuildStage.TypeMapping);
14 }
15 }
16 }
17
18
19
20 //PooledLifetimeManager.cs
21 using System;
22 using System.Collections.Generic;
23 using Microsoft.Practices.Unity;
24 using Microsoft.Practices.ObjectBuilder2;
25
26 namespace YourNameHere.Infrastructure.Unity
27 {
28 public class PooledLifetimeManager : LifetimeManager
29 {
30 readonly PooledLifetimeManagerPool Pool;
31
32 public PooledLifetimeManager(int maxPoolSize)
33 {
34 Pool = new PooledLifetimeManagerPool(maxPoolSize);
35 }
36
37 internal PooledLifetimeManagerPoolConsumer CreatePoolUserForChildContainer()
38 {
39 return new PooledLifetimeManagerPoolConsumer(Pool);
40 }
41
42 #region GetValue/SetValue/RemoveValue all redundant
43 public override object GetValue()
44 {
45 throw new InvalidOperationException();
46 }
47
48 public override void SetValue(object newValue)
49 {
50 throw new InvalidOperationException();
51 }
52
53 public override void RemoveValue()
54 {
55 throw new InvalidOperationException();
56 }
57 #endregion
58 }
59
60 internal class PooledLifetimeManagerPool
61 {
62 readonly int MaxPoolSize;
63 readonly object SyncRoot = new object();
64 readonly Queue<object> Queue = new Queue<object>();
65
66 public PooledLifetimeManagerPool(int maxPoolSize)
67 {
68 if (maxPoolSize < 0)
69 throw new ArgumentOutOfRangeException(
70 "MaxPoolSize", "Cannot be less than zero");
71 MaxPoolSize = maxPoolSize;
72 }
73
74 internal object RetrieveFromPool()
75 {
76 object result = null;
77 lock (SyncRoot)
78 if (Queue.Count > 0)
79 result = Queue.Dequeue();
80 IPooledResource resultAsIPooledResource = result as IPooledResource;
81 if (resultAsIPooledResource != null)
82 resultAsIPooledResource.RetrievedFromPool();
83 return result;
84 }
85
86 internal void ReturnToPool(object instance)
87 {
88 if (instance == null)
89 throw new ArgumentNullException("Instance");
90 IPooledResource instanceAsIPooledResource = instance as IPooledResource;
91 if (instanceAsIPooledResource != null)
92 {
93 if (!instanceAsIPooledResource.CanReturnToPool)
94 return;
95 instanceAsIPooledResource.ReturningToPool();
96 }
97 bool wentIntoPool = false;
98 lock (SyncRoot)
99 if (Queue.Count < MaxPoolSize)
100 {
101 Queue.Enqueue(instance);
102 wentIntoPool = true;
103 }
104
105 //Dispose any instances which did not go back into the pool
106 if (!wentIntoPool && instance is IDisposable)
107 ((IDisposable)instance).Dispose();
108 }
109 }
110
111 public class PooledLifetimeManagerPoolConsumer :
112 ContainerControlledLifetimeManager, IDisposable
113 {
114 readonly PooledLifetimeManagerPool SharedPool;
115 object Instance;
116
117 internal PooledLifetimeManagerPoolConsumer(
118 PooledLifetimeManagerPool sharedPool)
119 {
120 if (sharedPool == null)
121 throw new ArgumentNullException("SharedPool");
122 SharedPool = sharedPool;
123 }
124
125 protected override object SynchronizedGetValue()
126 {
127 if (Instance == null)
128 Instance = SharedPool.RetrieveFromPool();
129 return Instance;
130 }
131
132 protected override void SynchronizedSetValue(object newValue)
133 {
134 if (Instance != null && newValue == null)
135 SharedPool.ReturnToPool(Instance);
136 Instance = newValue;
137 }
138
139 public override void RemoveValue()
140 {
141 throw new InvalidOperationException();
142 }
143
144 void IDisposable.Dispose()
145 {
146 SetValue(null);
147 base.Dispose();
148 }
149 }
150 }
151
152
153
154 //PooledLifetimeStrategy.cs
155 using Microsoft.Practices.ObjectBuilder2;
156
157 namespace YourNameHere.Infrastructure.Unity
158 {
159 public class PooledLifetimeStrategy : BuilderStrategy
160 {
161 public override void PreBuildUp(IBuilderContext context)
162 {
163 PooledLifetimeManager activeLifetime =
164 context.PersistentPolicies.Get<ILifetimePolicy>(context.BuildKey)
165 as PooledLifetimeManager;
166 if (activeLifetime != null)
167 {
168 // did this come from the local container or the parent?
169 var localLifetime =
170 context.PersistentPolicies.Get<ILifetimePolicy>(
171 context.BuildKey, true);
172 if (localLifetime == null)
173 {
174 // came from parent, add a ContainerControlledLifetime here
175 var newLifeTime = activeLifetime.CreatePoolUserForChildContainer();
176 context.PersistentPolicies.Set<ILifetimePolicy>(
177 newLifeTime, context.BuildKey);
178 //Make sure it gets disposed
179 //so that it can return its reference to the pool
180 context.Lifetime.Add(newLifeTime);
181 }
182 }
183 }
184 }
185 }
186
187
188
189 //PooledResourceIntf.cs
190 namespace YourNameHere.Infrastructure.Unity
191 {
192 public interface IPooledResource
193 {
194 bool CanReturnToPool { get; }
195 void RetrievedFromPool();
196 void ReturningToPool();
197 }
198 }
199