c#: как реализован Monitor.Wait?

Как реализован метод Monitor.Wait() внутри класса system.threading.monitor C#?

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

И Lock(o), конечно же, является встроенным ярлыком С# для:

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

person Bimo    schedule 17.10.2017    source источник
comment
System.Threading.Monitor   -  person Tigran    schedule 17.10.2017
comment
Насколько я могу судить, статья, на которую ссылается ваш вопрос, эффективно отвечает на него. Какую дополнительную информацию вы ищете?   -  person DiskJunky    schedule 17.10.2017
comment
Чтобы лучше понять функцию ожидания, мне было любопытно, можно ли повторно реализовать ее как простую функцию, используя то, что уже предоставлено в виде опубликованных API-интерфейсов .Net. Это легче понять как просто код...   -  person Bimo    schedule 17.10.2017
comment
Тогда я надеюсь, что ваш C хорош ... ;) Я обновлю свой ответ в ближайшее время ...   -  person DiskJunky    schedule 17.10.2017


Ответы (1)


Чтобы действительно увидеть, есть ли более простой способ реализации Monitor.Wait, нам нужно исследовать, как он работает на низком уровне. Фактическая реализация в конечном итоге написана на 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. Согласно ссылке Тиграна на источник объекта 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)

Описание не требует пояснений относительно того, что он делает и в какой последовательности. Однако конкретная реализация, с помощью которой он это делает, основана на различных private static extern методах, содержащих ключевой код.

extern указывает, что фактическая реализация заключается в другая сборка. Он может использоваться с DllImport при доступе к неуправляемому коду (здесь это не так) или может быть внешний псевдоним. Отсюда, согласно сообщению SO спрашивая о том, где найти реализацию внешних методов, вам нужно посмотреть на C сам код, который можно найти в Core CLR (кредит Скотта Чемберлена).

Отсюда мы рассмотрим реализацию метода C для ObjWait(), который сопоставляет (строка 1027) с ObjectNative::WaitTimeout в 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

Прежде чем углубляться в это, стоит просмотреть это. (также кредит Скотта Чемберлена), в котором говорится;

FCalls идентифицируются в управляемом коде как внешние методы с установленным битом MethodImplOptions.InternalCall.

Это объясняет нашу ссылку на ObjWait() и ObjectNative::WaitTimeout. Итак, разбивая это дальше, мы можем увидеть основные null и проверки аргументов с соответствующими исключениями, возникающими, если это так. Суть в вызове pThis->Wait()...в этот момент я не могу проследить дальше...пока.

Отсюда мы переходим к Object::Wait (строка 531), затем перейдите к SyncBlock::Wait (строка 3442). На данный момент у нас есть большая часть мяса реализации, и это совсем немного.

Учитывая все вышесказанное и возвращаясь к тому, что вы просили о более простой реализации, я бы с осторожностью относился к упрощению Monitor.Wait(). Под капотом происходит много, и было бы очень легко допустить ошибку и получить потенциальные ошибки в альтернативной реализации.

ИЗМЕНИТЬ

Серьезный привет Скотту Чемберлену, который провел большую часть расследования ниже уровня ILSpy и копался/отлаживал стек кода C . Практически вся исследовательская работа ниже уровня ILSpy принадлежит ему, я просто собрал ее здесь в ответ.

person DiskJunky    schedule 17.10.2017
comment
@DiskJunky Я начал публиковать ту же цепочку, которую вы нашли, но врезался в стену при вызове pThis->Wait(Timeout, exitContext); в этом методе. - person D Stanley; 17.10.2017
comment
@DStanley да, я только что сам ударил. Я обновлю свой ответ выводами на сегодняшний день, пока буду продолжать расследование. - person DiskJunky; 17.10.2017
comment
Ваше предположение о подождите неверно, я получил решение для coreclr и работает, поэтому я могу нажать «Перейти к определению». pThis->Wait ведет на страницу Object::Wait с GetHeader()->Wait(timeOut, exitContext), который приведет вас к ObjHeader::Wait, надеюсь это поможет вам достаточно, чтобы продолжать отслеживать вещи. - person Scott Chamberlain; 18.10.2017
comment
Сделал еще один слой, ObjHeader::Wait имеет pSB->Wait(timeOut,exitContext), который ведет к SyncBlock::Wait, в этом методе довольно много мяса, поэтому я остановился на нем. - person Scott Chamberlain; 18.10.2017
comment
@ScottChamberlain, буду честен - это далеко за пределами моего понимания! Тем не менее, я буду продолжать обновлять ответ с любыми подробностями и отмечать вас. Мне хочу докопаться до сути... там много чего происходит в этом Monitor.Wait() - person DiskJunky; 18.10.2017
comment
Самый простой способ просмотреть код — клонировать репозиторий, создайте его, затем откройте проект Visual Studio для управляемого или нативного кода (вам нужно запустить сборку один раз, чтобы bin\obj\Windows_NT.<Arch>.<BuildType>\CoreCLR.sln существовало для просмотра нативного кода). это позволяет вам использовать такие инструменты, как Go to Definition, чтобы прыгать. - person Scott Chamberlain; 18.10.2017