Почему у нас здесь нет тупика? - PullRequest
2 голосов
/ 03 марта 2020

Пока я проверял работу своего кода, я обнаружил очень интересный фрагмент кода, который, клянусь, может вызвать тупик, но я проверял его много раз с несколькими потоками и не смог его получить.

это меня так озадачивает, что я решил спросить это здесь.
Итак, предположение, что LOCK не блокируется для того же потока, но я хотел бы подтвердить это.
После фрагмента кода

public class SplitService : ISplitService
{
    private IRecordService recordService;

    public SplitService(IRecordService recordService)
    {
        this.recordService = recordService;
    }

    private ConcurrentQueue<Batch> _batches = new ConcurrentQueue<Batch>();

    public void Feed(Something r)
    {
        lock (this.recordService)
        {
            if (!this.recordService.CanAppend(r))
            {
                Flush();
            }
            this.recordService.Append(r);
        }           
    }

    public void Flush()
    {
        lock (this.recordService)
        {
            if (!this.recordService.Any()) return;

            var record = this.recordService.GetBatch();
            _batches.Enqueue(record);

            this.recordService.Clean();
        }           
    }

    public IEnumerable<Batch> Get()
    {
        while (_batches.Any())
        {
            if (_batches.TryDequeue(out Batch batch))
            {
                yield return batch;
            }
        }
    }
}

Как вы можете заметить, метод Feed блокируется в объекте , и если то же самое возвращает false для CanAppend, он вызывает метод Flush, который также попробуйте заблокировать тот же объект . Так что я бы ожидал там тупика


После некоторого понимания экстраполяции, поскольку Lock рекурсивен, мы можем предположить, что это также будет работать:

lock(locker){           
   Console.WriteLine("Hello World");
   await new Task(() => {
        lock(locker){                   
            Console.WriteLine("Hello World from locker");
        }
   });
}

Ответы [ 2 ]

6 голосов
/ 03 марта 2020

Monitor объекты C# являются рекурсивными, поэтому вам просто нужно помнить, чтобы разблокировать их столько раз, сколько вы их блокируете. Например, это совершенно верно:

lock(someObject)
{
  lock(someObject)
  {
    lock(someObject)
    {
       Consolw.WriteLine("hello world")
    }
  }
}

Важно понимать, что блокировка рекурсивна только после ее получения. Если поток A получил блокировку, а затем поток B пытается получить блокировку, то B будет блокироваться, пока поток A не снимет блокировку.

3 голосов
/ 03 марта 2020

Для тупика вам понадобится 2 средства доступа и 2 ресурса. В этом случае есть только один ресурс, поэтому все будут терпеливо ждать его.

...