Anyone who has used Microsoft Project for time tracking is probably familiar with terms like Finish-to-Shart and Start-to-Start. For example, in a Waterfall model, we say that Requirements have to ‘finish’ (i.e., we must have them) before Coding starts.
The same idea also appears in multithreading – having a process begin only when another has started or is finished. In an ideal world, such problems in C# would be resolved using a clear, sensible API. Unfortunately, even waiting on several processes to complete is not easy. Here’s an illustration:
ThreadStart delA = a;
ThreadStart delB = b;
IAsyncResult arA = delA.BeginInvoke(null, null);
IAsyncResult arB = delB.BeginInvoke(null, null);
// no, you cannot have a pony
WaitHandle.WaitAll(new[]{ arA.AsyncWaitHandle, arB.AsyncWaitHandle});
That’s right – the neat API features such as WaitAll() are unusable simply because it only works under [MtaThread], which ruins the fun for far too many people to be useful.
Pulse & Wait
The static methods Pulse() and Wait() of the Minitor class are a solution to our problem of waiting on many concurrent operations to complete. That covers the finish-to-start scenario, but these mechanisms work for the start-to-start scenario, too. Let’s see how.
Imagine you’re making breakfast, consisting of jam on toast and a cup of tea. Now, toasting bread and making tea are async operations; you can only try to take the jam out once you’ve started toasting the bread; you can only have breakfast when you’ve got jam on toast and a cup of tea. Here’s a graphic representation:

Just in case you haven’t guessed, solid lines are finish-to-start ones, while the dashed line is start-to-start. So how would code for such a scenario look? Well, the first thing you must realize with Wait() and Pulse() is that every operation that waits on something has a lock:
private readonly object MakeSandwichLock = new object();
private readonly object EatBreakfastLock = new object();
private readonly object GetJamLock = new object();
Now, pay attention, here’s the second part of the puzzle: every operation that someone waits on has a bool indicating whether it is done:
private bool MakeTeaIsDone;
private bool ToastBreadIsDone;
private bool GetJamIsDone;
private bool MakeSandwichIsDone;
Okay, if you look at the diagram, the two parts of nomenclature I just mentioned should be obvious. MakeSandwich, EatBreakfast and GetJam all wait on someone – although GetJam does a ‘soft wait’ because it waits for something to start and not finish. Then each function that is waited upon got a bool flag to indicate whether it is done. Hey, we need some indicator, right?
Now here’s the third part of the puzzle: both finish-to-shart and start-to-start operations are enforced by a)locking on your lock; b) doing a Monitor.Wait() if the combination of boolean flags is unsatisfactory. Here’s some code for actually eating the breakfast:
protected internal void EatBreakfast()
{
lock(EatBreakfastLock)
if(!(MakeTeaIsDone && MakeSandwichIsDone))
Monitor.Wait(EatBreakfastLock);
EatBreakfastImpl();
}
Just to reiterate: to get to eat breakfast, we a)lock on the breakfast lock; and b)wait on that same lock if we cannot proceed yet. Okay, we’re almost there – all we need to know is when to actually set those boolean flags and Pulse(). Well, this depends: if you are doing a start-to-start, you do it at the start of the method; otherwise, do it at the end. Do what? Well, to inform someone you’re done, lock on the target lock (i.e., on the lock of someone waiting on you), then set your Done boolean to true, then Pulse(). For example, after tea is ready, here’s how to tell the EatBreakfast method that it can (theoretically) proceed:
protected internal void MakeTea()
{
MakeTeaImpl();
lock(EatBreakfastLock)
{
MakeTeaIsDone = true;
Monitor.PulseAll(EatBreakfastLock);
}
}
Ordering
Before we move on, here’s the whole Breakfast class (without the Impl() methods, of course):
partial class Breakfast
{
private readonly object MakeSandwichLock = new object();
private readonly object EatBreakfastLock = new object();
private readonly object GetJamLock = new object();
private bool MakeTeaIsDone;
private bool ToastBreadIsDone;
private bool GetJamIsDone;
private bool MakeSandwichIsDone;
private bool ToastBreadStarted;
protected internal void MakeTea()
{
MakeTeaImpl();
lock(EatBreakfastLock)
{
MakeTeaIsDone = true;
Monitor.PulseAll(EatBreakfastLock);
}
}
protected internal void ToastBread()
{
lock(GetJamLock)
{
ToastBreadIsDone = true;
Monitor.PulseAll(GetJamLock);
}
ToastBreadImpl();
lock(MakeSandwichLock)
{
ToastBreadIsDone = true;
Monitor.PulseAll(MakeSandwichLock);
}
}
protected internal void GetJam()
{
lock(GetJamLock)
if(!(ToastBreadStarted))
Monitor.Wait(GetJamLock);
GetJamImpl();
lock(MakeSandwichLock)
{
GetJamIsDone = true;
Monitor.PulseAll(MakeSandwichLock);
}
}
protected internal void MakeSandwich()
{
lock(MakeSandwichLock)
if(!(ToastBreadIsDone && GetJamIsDone))
Monitor.Wait(MakeSandwichLock);
MakeSandwichImpl();
lock(EatBreakfastLock)
{
MakeSandwichIsDone = true;
Monitor.PulseAll(EatBreakfastLock);
}
}
protected internal void EatBreakfast()
{
lock(EatBreakfastLock)
if(!(MakeTeaIsDone && MakeSandwichIsDone))
Monitor.Wait(EatBreakfastLock);
EatBreakfastImpl();
}
}
So, how does this monstrosity get fired off? Well, here’s one possibility:
ThreadStart[] ops = new ThreadStart[] {
MakeTea,
GetJam,
ToastBread,
MakeSandwich,
EatBreakfast };
foreach (ThreadStart op in ops)
op.BeginInvoke(null, null);
There’s one caveat with all of this: what if we had ToastBread before GetJam in the list? That’s right – you would have a hanging application. It’s important to remember that Pulse() is only useful is someone is Wait()ing on it. If you cause a situation where Pulse() is fired before the Wait()ing code is running, you’re in trouble. Luckily, it’s a fairly obvious mistake that you’re likely to spot even if you’re not into unit tests.
In Closing
I don’t seriously recommend anyone writing code like the one above – in fact, the above picture and code come from a DSL that facilitates P&W-based asynchronous design and a lot more.