In a recent project that I’ve been working on, I had the need to access a singleton object from multiple callers on multiple threads, but then restrict access to the singleton during a change operation, during which the singleton was changed out and replaced with a new one. New requests must block until the singleton change is complete, but existing requests must complete before the singleton change occurs.
A WaitHandle – specifically, an EventWaitHandle with the reset mode set to Manual – is an effective way to allow an arbitrary number of threads go through the primitive until its reset condition is met. In this case, however, because we needed to perform custom behaviors for using Set() and Reset() for domain-specific reasons, I contained the underlying EventWaitHandle within my derived class and simply derived the class from WaitHandle. This class is called SynchronizedReferenceCounter:
1: using System;
2: using System.Threading;
4: namespace Terralever.Threading
6: public class SynchronizedReferenceCounter : WaitHandle
8: private EventWaitHandle m_handle;
9: private int m_refCount;
11: public SynchronizedReferenceCounter()
13: m_handle = new EventWaitHandle(true, EventResetMode.ManualReset);
16: public void AddReference()
20: Interlocked.Increment(ref m_refCount);
23: public void RemoveReference()
25: if (Interlocked.Decrement(ref m_refCount) == 0)
31: public override bool WaitOne()
33: return m_handle.WaitOne();
36: public override bool WaitOne(int millisecondsTimeout, bool exitContext)
38: return m_handle.WaitOne(millisecondsTimeout, exitContext);
41: public override bool WaitOne(TimeSpan timeout, bool exitContext)
43: return m_handle.WaitOne(timeout, exitContext);
46: public void AllowNewReferences()
51: public void StopNewReferences()
56: public event EventHandler ZeroReferencesInUse;
57: protected virtual void OnZeroReferencesInUse(EventArgs e)
59: if (ZeroReferencesInUse != null)
60: ZeroReferencesInUse.BeginInvoke(this, e, null, null);
In this class, I override the WaitOne() methods and marshal them to the contained EventWaitHandle’s WaitOne() methods as appropriate. Reference additions are blocked whenever the contained EventWaitHandle is in a Reset state, when defined by a call to the StopNewReferences() method. An object that contains this reference counter should call AddReference() whenever it is accessed, and RemoveReference() whenever it is released. For example:
1: public class ReferenceCountedSingleton
3: // ...
4: public static ReferenceCountedSingleton GetCurrent()
7: return s_current;
10: public static void ReleaseReference()
15: public static void InitiateChange()
20: private static void s_counter_ZeroReferencesInUse(object sender, EventArgs e)
22: // finish the change
This shell class assumes a few things, but should be fairly straightforward.
There is some information that you should know about, though. One thing that might strike you is that if ReleaseReference() isn’t called, you can run into a deadlock scenario. This can be solved by ensuring that you encapsulate this functionality in a try/finally block:
3: ReferenceCountedSingleton instance = ReferenceCountedSingleton.GetCurrent();
4: // perform operations that could be risky here.
As shown here, it is important to access the reference-counted variable only once, and so you should take a local variable of the instance. To do so, declare the local variable as an instance.
The issue you can potentially run into here is that singletons tend to do their magic via the Instance property like so:
If you do this perilous series of lines, you’ll increment the counter three times. If you only call ReleaseReference() once, you’ll run into a deadlock scenario where you’ll never get to a zero-reference count, and you’ll never finish your action.
Hopefully this helps someone. It took a lot of research (coming soon) to find the right synchronization primitive to use for this scenario.