Mutexes, events and threads, oh my! Synchronization using a task based asynchronous approach
One of the more difficult problems when trying to synchronize across threads and processes on Windows is not only choosing the right kernel object, but also constraining the lifetime and signalling context of said same object(s).
Lets suppose we have an GUI application (which provides us with your typical potpourri of threading and synchronization context issues), and a console monitoring application with which we want to communicate some event. Lets suppose further that our GUI application will be running many background threads, any of which will actually be the thread which will need to signal the monitoring console process. It would look something like this:
Immediately several issues arise:
1. The mutex object would have to be created on a separate thread in the process so as not to block the main foreground thread (more on this later).
2. The mutex object must be signaled on the same thread that was used to create it!
There are two approaches that can be taken to tackle this problem:
The first approach would be to create a separate thread that is responsible for mutex creation and signalling. The code would look something like this:
Here, we first create our object that contains the mutex. This object also contains an event object which can be signaled from any thread. This allows anyone to unblock the thread proc when we want to release the mutex, but guarantee that the original thread that created the mutex is the one that signals the mutex.
Signalling becomes a matter of setting the event object. We can do that in a UI thread as:
or in a background thread as:
OK, that's all well and fine, but this is 2018, and we don't really do threads this way anymore... at least we shouldn't be doing threads this way anymore : )
Task Based Asynchronous Approach
Instead, lets leverage the Task Parallel Library (TPL) to do the heavy lifting. Lets rewrite this so that we don't actually create any threads at all.
The mutex signal synchronization object is initialized as:
So far it seems unremarkable. Or is it? Did you miss it? Look again at the following line above:
This is the secret sauce that makes this whole thing work! Lets look at WaitAsync:
As soon as we hit WaitAny(...), we immediately block and return from WaitAsync, which immediately returns from InitAndWait. What we have achieved is mutex creation and signalling on the same thread (in truth, we don't know, or care, if the TPL creates a new thread or uses the existing thread -- all we care about is that we create the mutex, block and immediately return, continuing on our merry way).
Signalling is now a matter of either:
Background thread signal...
Note: Sample source can be found in my .git repository here: