Tight Coupling
Once upon a time, in a strange land south of here, there was a worker named
Peter. He was a diligent worker who would readily accept requests from his boss.
However, his boss was a mean, untrusting man who insisted on steady progress
reports. Since Peter did not want his boss standing in his office looking over
his shoulder, Peter promised to notify his boss whenever his work progressed.
Peter implemented this promise by periodically calling his boss back via a typed
reference like so:
class Worker {
public void Advise(Boss boss) { _boss = boss; }
public void DoWork() {
Console.WriteLine("Worker: work started");
if( _boss != null ) _boss.WorkStarted();
Console.WriteLine("Worker: work progressing");
if( _boss != null ) _boss.WorkProgressing();
Console.WriteLine("Worker: work completed");
if( _boss != null ) {
int grade = _boss.WorkCompleted();
Console.WriteLine("Worker grade= " + grade);
}
}
private Boss _boss;
}
class Boss {
public void WorkStarted() { }
public void WorkProgressing() { }
public int WorkCompleted() {
Console.WriteLine("It's about time!");
return 2;
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.Advise(boss);
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
Interfaces
Now Peter was a special person. Not only was he able to put up with his
mean-spirited boss, but he also had a deep connection with the universe around
him. So much so that he felt that the universe was interested in his progress.
Unfortunately, there was no way for Peter to advise the Universe of his progress
unless he added a special Advise method and special callbacks just for the
Universe, in addition to keeping his boss informed. What Peter really wanted to
do was to separate the list of potential notifications from the implementation
of those notification methods. And so he decided to split the methods into an
interface:
interface IWorkerEvents {
void WorkStarted();
void WorkProgressing();
int WorkCompleted();
}
class Worker {
public void Advise(IWorkerEvents events) { _events = events; }
public void DoWork() {
Console.WriteLine("Worker: work started");
if( _events != null ) _events.WorkStarted();
Console.WriteLine("Worker: work progressing");
if(_events != null ) _events.WorkProgressing();
Console.WriteLine("Worker: work completed");
if(_events != null ) {
int grade = _events.WorkCompleted();
Console.WriteLine("Worker grade= " + grade);
}
}
private IWorkerEvents _events;
}
class Boss : IWorkerEvents {
public void WorkStarted() { }
public void WorkProgressing() { }
public int WorkCompleted() {
Console.WriteLine("It's about time!");
return 3;
}
}
Delegates
Unfortunately, Peter was so busy talking his boss into implementing this
interface that he didn't get around to notifying the Universe, but he knew he
would soon. At least he'd abstracted the reference of his boss far away from him
so that others who implemented the IWorkerEvents interface could be notified of
his work progress.
Still, his boss complained bitterly. "Peter!" his boss fumed.
"Why are you bothering to notify me when you start your work or when your
work is progressing?!? I don't care about those events. Not only do you force me
to implement those methods, but you're wasting valuable work time waiting for me
to return from the event, which is further expanded when I am far away! Can't
you figure out a way to stop bothering me?"
And so, Peter decided that while interfaces were useful for many things, when
it came to events, their granularity was not fine enough. He wished to be able
to notify interested parties only of the events that matched their hearts'
desires. So, he decided to break the methods out of the interface into separate
delegate functions, each of which acted like a little tiny interface of one
method each:
delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();
class Worker {
public void DoWork() {
Console.WriteLine("Worker: work started");
if( started != null ) started();
Console.WriteLine("Worker: work progressing");
if( progressing != null ) progressing();
Console.WriteLine("Worker: work completed");
if( completed != null ) {
int grade = completed();
Console.WriteLine("Worker grade= " + grade);
}
}
public WorkStarted started;
public WorkProgressing progressing;
public WorkCompleted completed;
}
class Boss {
public int WorkCompleted() {
Console.WriteLine("Better...");
return 4;
}
}
class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
Static Listeners
This accomplished the goal of not bothering his boss with events that he
didn't want, but still Peter had not managed to get the universe on his list of
listeners. Since the universe is an all-compassing entity, it didn't seem right
to hook delegates to instance members (imagine how many resources multiple
instances of the universe would need...). Instead, Peter need to hook delegates
to static members, which delegates support fully:
class Universe {
static void WorkerStartedWork() {
Console.WriteLine("Universe notices worker starting work");
}
static int WorkerCompletedWork() {
Console.WriteLine("Universe pleased with worker's work");
return 7;
}
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.started = new WorkStarted(Universe.WorkerStartedWork);
peter.completed = new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
Events
Unfortunately, the Universe being very busy and unaccustomed to paying
attention to individuals, has managed to replace Peter's boss's delegate with
its own. This is an unintended side effect of making the delegate fields public
in Peter's Worker class. Likewise, if Peter's boss gets impatient, he can decide
to fire Peter's delegates himself (which is just the kind of rude thing that
Peter's boss was apt to do):
if( peter.completed != null ) peter.completed();
Peter wants to make sure that neither of these can happens. He realizes he
needs to add registration and unregistration functions for each delegate so that
listeners can add or remove themselves, but can't clear the entire list or fire
Peter's events. Instead of implementing these functions himself, Peter uses the
event keyword to make the C# compiler build these methods for him:
class Worker {
...
public event WorkStarted started;
public event WorkProgressing progressing;
public event WorkCompleted completed;
}
Peter knows that the event keyword erects a property around a delegate, only
allowing C# clients to add or remove themselves with the += and -= operators,
forcing his boss and the universe to play nicely:
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed += new WorkCompleted(boss.WorkCompleted);
peter.started += new WorkStarted(Universe.WorkerStartedWork);
peter.completed += new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
Harvesting All Results
At this point, Peter breathes a sign of relief. He has managed to satisfy the
requirements of all his listeners without having to be closely coupled with the
specific implementations. However, he notices that while both his boss and the
universe provide grades of his work that he's only receiving one of the grades.
In the face of multiple listeners, he'd really like to harvest all of their
results. So, he reaches into his delegate and pulls out the list of listeners so
that he can call each of them manually:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
int grade = wc();
Console.WriteLine("Worker grade= " + grade);
}
}
}
Async Notification: Fire & Forget
In the meantime, his boss and the universe have been distracted with other
things, which means that the time it takes them to grade Peter's work is greatly
expanded:
class Boss {
public int WorkCompleted() {
System.Threading.Thread.Sleep(3000);
Console.WriteLine("Better..."); return 6;
}
}
class Universe {
static int WorkerCompletedWork() {
System.Threading.Thread.Sleep(4000);
Console.WriteLine("Universe is pleased with worker's work");
return 7;
}
...
}
Unfortunately, since Peter is notifying each listener one at a time, waiting
for each to grade him, these notifications now take up quite a bit of his time
when he should be working. So, he decides to forget the grade and just fire the
event asynchronously:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() )
{
wc.BeginInvoke(null, null);
}
}
}
Async Notification: Polling
This allows Peter to notify the listeners while letting Peter get back to
work immediately, letting the process thread pool invoke the delegate. Over
time, however, Peter finds that he misses the feedback on his work. He knows
that he does a good job and appreciates the praise of the universe as a whole
(if not his boss specifically). So, he fires the event asynchronously, but polls
periodically, looking for the grade to be available:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
IAsyncResult res = wc.BeginInvoke(null, null);
while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}
}
}
Async Notification: Delegates
Unfortunately, Peter is back to what he wanted his boss to avoid with him in
the beginning, i.e. looking over the shoulder of the entity doing the work. So,
he decides to employ his own delegate as a means of notification when the async
delegate has completed, allowing him to get back to work immediately, but still
be notified when his work has been graded:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);
}
}
}
private void WorkGraded(IAsyncResult res) {
WorkCompleted wc = (WorkCompleted)res.AsyncState;
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}
Happiness in the Universe
Peter, his boss and the universe are finally satisfied. Peter's boss and the
universe are allowed to be notified of the events that interest them, reducing
the burden of implementation and the cost of unnecessary round-trips. Peter can
notify them each, ignoring how long it takes them to return from their target
methods, while still getting his results asynchronously. Peter knows that it's
not quite that easy, because as soon as he fires events asynchronously, the
target methods are likely to be executed on another thread, as is Peter's
notification of when the target method has completed. However, Peter
is good friends with Mike,
who is very familiar with threading issues and can provide guidance in that
area.