Introduction
The pub/sub pattern is a reoccurring pattern used by almost all non trivial application. Weak references come in very handy to avoid zombie subscribers causing memory leaks, wasted CPU cycles and other nasty bugs.
The following code example shows a WeakSubscriptionCollection
class that uses weak references and can be used in Portable Class Libraries (PCL). I developed the code for a Xamarin project to implement an application for iOS and Android. (This code can be used with profile 7 or profile 78.)
Background
The pub/sub pattern is a pattern often used for messaging components that want to notify other components if a change in the system happened. The basic idea is that a subscriber subscribes to a publisher by providing a callback function that the publisher stores until the publisher wants to notify the subscriber of a specific state change in the system. So the main functionality of the publisher is to keep a list of subscribers and to allow subscribers to add and remove subscriptions to the list and to call subscribers on the provided callback method when a specific state change happened.
In a system with a garbage collector such as .NET or Java, an object is automatically removed from memory (garbage collected) if no other object holds a (strong) reference to that object. Weak References can be used to hold a reference to an object that will still allow garbage collection if there is no strong reference to that object.
By using weak references for the list of subscribers in a pub/sub implementation, subscribers can be collected if they are only referenced by the list of subscriptions of the publisher and therefore avoiding zombie objects being referenced by the publisher. In addition, the publisher can detect if a target of the weak reference is no longer alive and remove the dead's object subscription from its list of subscribers to keep the list clean with only alive subscribers.
Using the Code
Callbacks or Delegates in C# cannot be weak referenced. The reason is that the C# compiler will generate a new instance of the callback when passed to the subscriber. If the only reference that holds on to the passed in delegate is a weak reference, the garbage collector will collect it even though the target of the delegate is still alive. Therefore we have to perform a simple trick. We need to weak reference the target of the callback and store the information about the method on the target that should be called.
Please note that the code is simplified and synchronization objects, try
/catch
/finally
blocks and some cleanup code has been omitted for clarity!
List<WeakReference<object>> listOfSub;
var methodInfo = onChange.GetMethodInfo();
if (!_subscriptions.TryGetValue(methodInfo, out listOfSub))
{
listOfSub = new List<WeakReference<object>>();
_subscriptions.Add(methodInfo, listOfSub);
}
listOfSub.Add(new WeakReference<object>(onChange.Target));
The central piece of most (or probably all) publisher is the list of subscribers. In order to allow async execution and thread safety, I implemented method pairs (xyzBegin
and xyzEnd
) for all the operations on the collection. A possible optimization could be to avoid the locks by using Concurrent collections or Immutable
lists.
The main operations of the publisher are: Add Subscribers, Remove Subscribers and Invoke Subscribers:
public class WeakSubscriptionCollection
{
public void AddBegin(Delegate onChange){...}
public void AddEnd() {...}
public void InvokeBegin() {...}
public void InvokeEnd(object[] args) {...}
public void RemoveBegin(Delegate onChange) {...}
public void RemoveEnd() {...}
}
The idea is that any publisher class can use the SubscriptionCollection
to manage the list of subscribers. This makes the Publisher very simple to implement.
A publisher could be implemented like this:
public class PubExample
{
public void Subscribe(Action<int,int> onChange)
{
_subscribers.AddBegin(onChange);
if (_subscribers.Count == 1)
{
_service.Connect(
() => _subscribers.AddEnd(),
onChangeInternal);
}
else
{ onChange(currentInt1,currentInt2);
_subscribers.AddEnd();
}
}
private void onChangeInternal(int val1, int val2)
{
_subscribers.InvokeBegin();
currentVal1 = val1;
currentVal2 = val2;
object[] args = { val1, val2 }
_subscribers.InvokeEnd(args);
}
public void Unsubscribe(Action<List<PartyEntity>, List<PartyEntity>, List<Guid>, bool> onChange)
{
_subscribers.RemoveBegin(onChange);
if (_subscribers.Count == 0)
_service.Disconnect();
_subscribers.RemoveEnd();
}
A subscriber subscribing to the publisher could be implemented like this:
public class Subscriber
{
private PubExample publisher;
private void onChange(int val1, int val2)
{
}
public void Run()
{
publisher.Subscribe(onChange);
...
}
public void Terminate()
{
publisher.Unsubscribe(onChange);
}
}
What I want to point out is that the Subscriber can get garbage collected if no other object is referencing it even if the Subscriber did not unsubscribe. If the subscriptions would not weak reference the target, the Subscriber would never be collected because of the subscription.
Points of Interest
The WeakSubscriberCollection
is highly reusable and hides a lot of the code to manage the collections and invoking of the subscribers. That leads to a very simple implementation of the publisher. As mentioned earlier, the code is simplified for clarity.
History