2

How is the Monitor.Wait() method implemented inside of the system.threading.monitor class of C#?

https://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified

Conceptually, I'm envisioning something like this:

   class Monitor {
       public static Wait(object o) 
       {
           // Release Lock
           Monitor.Exit(o);

           // Spinlock until another Thread acquires Lock
           while(!Monitor.isEnter(o));

           // Wait to re-acquire lock
           Monitor.Enter(o);
       }
   }

It this accurate? Or Is there anything i'm missing?

Here's the typical monitor class example I'm considering as a bases for the preceding code.

using System.Threading;

readonly object o = new object();

// In Thread #1: (Where appropriate) 
lock(o) {
    Monitor.Wait(o);
}

//In Thread #2:  (Where appropriate) 
lock(o) {
    Monitor.Pulse(o);  
}

And Lock(o) is of course a built-in c# shortcut for:

try {
    Monitor.Enter(o);
    {
        //Lock Block Here
    }
}
finally {
    Monitor.Exit(o);
}
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 5
    [System.Threading.Monitor](http://referencesource.microsoft.com/#mscorlib/system/threading/monitor.cs) – Tigran Oct 17 '17 at 19:03
  • The article linked to in your question effectively answers it as far as I can tell. What extra info are you looking for? – DiskJunky Oct 17 '17 at 19:49
  • As a way to better understand the wait function, I was curious if its possible to re-implement it as simple function using what's already given as a published .Net APIs. It easier to understand as just code... – Bimo Oct 17 '17 at 19:56
  • Then I hope your C is good... ;) I'll update my answer in the next short while... – DiskJunky Oct 17 '17 at 20:03

1 Answers1

4

To truely see if there's a simpler way to implement Monitor.Wait, we'd ahve to investigate how it functions at a low level. The actual implementation is ultimately written in C and hidden from us but specifically for Monitor.Wait(object), we can trace the chain of calls in the following manner;

Monitor.Wait(o)
-- return Monitor.Wait(o, -1, false)

Monitor.Wait(o, -1, false)
-- Monitor.ObjWait(false [exitContext], -1 [millisecondsTimeout], o)

From here it's harder to see what's going on even in ILSpy. As per Tigran's link to the source of the Monitor object, we're left with the following in the source;

    /*========================================================================
** Waits for notification from the object (via a Pulse/PulseAll). 
** timeout indicates how long to wait before the method returns.
** This method acquires the monitor waithandle for the object 
** If this thread holds the monitor lock for the object, it releases it. 
** On exit from the method, it obtains the monitor lock back. 
** If exitContext is true then the synchronization domain for the context 
** (if in a synchronized context) is exited before the wait and reacquired 
**
    ** Exceptions: ArgumentNullException if object is null.
========================================================================*/
    [System.Security.SecurityCritical]  // auto-generated
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)]
    private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, Object obj)

The description is left fairly self-explanitory into what's it's doing and in what sequence. However, the precise implementation by which it does so is wrapped from various private static extern methods which contain the crucial code.

extern specifies that the actual implementation lies in another assembly. It may be used with DllImport when accessing unmanaged code (not the case here) or could be an extern alias. From here as per SO post asking about where to find the implementation of extern methods, you'd have to look at the C code itself which can be found in the Core CLR (credit Scott Chamberlain).

From here we're looking at the C method implementation for ObjWait() which maps (line 1027) to ObjectNative::WaitTimeout in the CLR;

FCIMPL3(FC_BOOL_RET, ObjectNative::WaitTimeout, CLR_BOOL exitContext, INT32 Timeout, Object* pThisUNSAFE)
{
    FCALL_CONTRACT;

    BOOL retVal = FALSE;
    OBJECTREF pThis = (OBJECTREF) pThisUNSAFE;
    HELPER_METHOD_FRAME_BEGIN_RET_1(pThis);

    if (pThis == NULL)
        COMPlusThrow(kNullReferenceException, W("NullReference_This"));

    if ((Timeout < 0) && (Timeout != INFINITE_TIMEOUT))
        COMPlusThrowArgumentOutOfRange(W("millisecondsTimeout"), W("ArgumentOutOfRange_NeedNonNegNum"));

    retVal = pThis->Wait(Timeout, exitContext);

    HELPER_METHOD_FRAME_END();
    FC_RETURN_BOOL(retVal);
}
FCIMPLEND

Before getting into this, it's worth looking at this (also credit Scott Chamberlain), which states;

FCalls are identified in managed code as extern methods with the MethodImplOptions.InternalCall bit set.

This explains our link to ObjWait() and ObjectNative::WaitTimeout. So, breaking this down further, we can see basic null and argument checks with appropriate exceptions raised if so. The crux is the call to pThis->Wait()...at which point I can't quite trace further...yet.

From here we get to Object::Wait (line 531), then going to SyncBlock::Wait (line 3442). At this point we have most of the meat of the implementation and there's quite a bit to it.

Given all of the above and getting back to what you asked re a simpler implementation, I'd be wary of simplifying Monitor.Wait(). There is a lot going on under the hood and it'd be very easy to make a mistake and have potential bugs in an alternative implementation.

EDIT

Serious shout out to Scott Chamberlain who did most of the investigation below ILSpy-level and delving/debugging the C code stack. Pretty much all investigatory work below ILSpy-level is his, I've merely compiled it here into an answer.

DiskJunky
  • 4,750
  • 3
  • 37
  • 66
  • 2
    https://github.com/dotnet/coreclr/blob/e0486761d6c019cb696dcd35aeecc031b1d01eef/src/classlibnative/bcltype/objectnative.cpp – D Stanley Oct 17 '17 at 19:49
  • 2
    @DiskJunky I started to post the same chain that you found, but hit a wall at the `pThis->Wait(Timeout, exitContext);` call in that method. – D Stanley Oct 17 '17 at 19:59
  • @DStanley yeah, I just hit that myself. I'll update my answer with findings to date while I investigate further – DiskJunky Oct 17 '17 at 20:04
  • 2
    Your guess about Wait is incorrect, I got the solution for coreclr up and running so I can hit Go to Definition. `pThis->Wait` takes you to [`Object::Wait`](https://github.com/dotnet/coreclr/blob/377073385e4545d36e1a96429dd78548f87c597c/src/vm/object.h#L531) which has a `GetHeader()->Wait(timeOut, exitContext)` that takes you to [`ObjHeader::Wait`](https://github.com/dotnet/coreclr/blob/e0486761d6c019cb696dcd35aeecc031b1d01eef/src/vm/syncblk.cpp#L2707), hopefully that will help you enough to keep tracking things down. – Scott Chamberlain Oct 17 '17 at 21:54
  • 2
    Did one more layer, `ObjHeader::Wait` has a `pSB->Wait(timeOut,exitContext)` that takes you to [`SyncBlock::Wait`](https://github.com/dotnet/coreclr/blob/e0486761d6c019cb696dcd35aeecc031b1d01eef/src/vm/syncblk.cpp#L3442), that method has quite a bit of meat to it so I stopped there. – Scott Chamberlain Oct 17 '17 at 22:00
  • @ScottChamberlain, I'll be honest - this is far out of my depth! That said, I'll keep updating the answer with any detail and credit yourself. I *am* curious to get to the bottom of this though...there's a lot going on in that `Monitor.Wait()` – DiskJunky Oct 17 '17 at 22:00
  • 2
    The easist way to look around the code is clone the repo, [build it](https://github.com/dotnet/coreclr/blob/e0486761d6c019cb696dcd35aeecc031b1d01eef/Documentation/building/windows-instructions.md) then open [the visual studio project](https://github.com/dotnet/coreclr/blob/e0486761d6c019cb696dcd35aeecc031b1d01eef/Documentation/workflow/EditingAndDebugging.md) for the managed or native code (you need to run a build once to get the `bin\obj\Windows_NT..\CoreCLR.sln` to exist to look around the native code). this lets you use tools like Go to Definition to jump around. – Scott Chamberlain Oct 17 '17 at 22:02