2017-10-17 116 views
2

如何在C#的system.threading.monitor类中实现Monitor.Wait()方法?c#:Monitor.Wait是如何实现的?

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

概念,我构想是这样的:

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); 
     } 
    } 

它这个准确吗?还是有什么我失踪?

下面是我正在考虑作为上述代码的基础的典型监视器类示例。

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); 
} 

并锁定(O)当然是一个内置的C#快捷方式:

try { 
    Monitor.Enter(o); 
    { 
     //Lock Block Here 
    } 
} 
finally { 
    Monitor.Exit(o); 
} 
+5

[System.Threading.Monitor](http://referencesource.microsoft.com/#mscorlib/system/threading/monitor.cs) – Tigran

+0

链接到您的问题的文章有效地回答它,据我所知。你在寻找什么额外的信息? – DiskJunky

+0

作为更好地理解等待函数的一种方式,我很好奇它是否可以使用已发布的.Net API作为简单函数重新实现它。它更容易理解为只是代码...... –

回答

4

要忠实地看看有没有实现Monitor.Wait一个更简单的方法,我们会ahve探讨如何功能处于较低水平。实际的实现最终是用C语言编写的,对我们来说是隐藏的,但是对于Monitor.Wait(object),我们可以用以下方式来追踪调用链;

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

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

从这里很难看出即使在ILSpy中发生了什么。根据Tigran与Monitor对象源的链接,我们在源代码中留下以下内容:

/*======================================================================== 
** 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) 

该描述是相当自我解释的,它是干什么的,以什么顺序。然而,它的确切实现是从包含关键代码的各种方法中打包出来的。

externspecifies实际执行在于另一个程序集。当访问非托管代码(这里不是这种情况)或可能是extern alias时,它可以与DllImport一起使用。从这里按照SO post asking about where to find the implementation of extern methods,你必须看看C代码本身,它可以在Core CLR(信誉Scott Chamberlain)中找到。

从这里我们看看在CLR中的ObjWait()的C方法实现,其中maps(第1027行)到ObjectNative::WaitTimeout;

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 

之前进入这个,这是值得看的this(也归功于斯科特·张伯伦),其中规定;

FCalls在托管代码中标识为外部方法,并设置了MethodImplOptions.InternalCall位。

这解释了我们与ObjWait()ObjectNative::WaitTimeout的链接。因此,进一步分解这个问题,我们可以看到基本的null和参数检查,如果有的话会引发适当的异常。关键是要拨打电话pThis->Wait() ......在这一点上,我无法进一步追踪......。

从这里我们得到Object::Waitline 531),然后去SyncBlock::Waitline 3442)。在这一点上,我们拥有大部分实施的内容,并且有相当多的内容。

鉴于上述所有,并又回到了你的要求再简单的实现,我会警惕简化Monitor.Wait()的。有一个很多正在引擎盖下,它会很容易犯一个错误,并在其他实现中有潜在的错误。

编辑

严重喊出Scott Chamberlain谁做的最下方ILSpy级和钻研的调查/调试C代码堆栈。几乎所有ILSpy级以下的调查工作都是他的,我只是把它编译成一个答案。

+2

https://github.com/dotnet/coreclr/ BLOB/e0486761d6c019cb696dcd35aeecc031b1d01eef/src目录/ classlibnative/bcltype/objectnative.cpp –

+2

@DiskJunky我开始后,你发现了同样的链条,但在'pThis->等待(超时,exitContext)撞上了一堵墙;在方法调用'。 –

+0

@DStanley是的,我自己打了那个。我会更新我的答案,并在调查时进一步调查 – DiskJunky