Есть ли класс синхронизации, который гарантирует порядок FIFO в С#?

Что это и как использовать?

Мне нужно это, поскольку у меня есть таймер, который вставляет в БД каждую секунду, и у меня есть общий ресурс между обработчиком таймера и основным потоком. Я хочу гарантировать, что если обработчик таймера занимает более одной секунды при вставке, ожидаемые потоки должны выполняться по порядку. Это пример кода для моего обработчика таймера

private void InsertBasicVaraibles(object param)
{
            try
            {
                DataTablesMutex.WaitOne();//mutex for my shared resources
                //insert into DB
            }
            catch (Exception ex)
            {
                //Handle
            }
            finally
            {
                DataTablesMutex.ReleaseMutex();
            }
}

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

Ответ 1

Для этого вам нужно написать свой собственный класс, я нашел этот пример (вставлен, потому что похоже, что домен сайта истек):

using System.Threading;

public sealed class QueuedLock
{
    private object innerLock;
    private volatile int ticketsCount = 0;
    private volatile int ticketToRide = 1;

    public QueuedLock()
    {
        innerLock = new Object();
    }

    public void Enter()
    {
        int myTicket = Interlocked.Increment(ref ticketsCount);
        Monitor.Enter(innerLock);
        while (true)
        {

            if (myTicket == ticketToRide)
            {
                return;
            }
            else
            {
                Monitor.Wait(innerLock);
            }
        }
    }

    public void Exit()
    {
        Interlocked.Increment(ref ticketToRide);
        Monitor.PulseAll(innerLock);
        Monitor.Exit(innerLock);
    }
}

Пример использования:

QueuedLock queuedLock = new QueuedLock();

try
{
   queuedLock.Enter();
   // here code which needs to be synchronized
   // in correct order
}
finally
{
    queuedLock.Exit();
}

Источник через кеш Google

Ответ 2

Просто прочитав Joe Duffy "Параллельное программирование в Windows", похоже, что вы обычно получаете поведение FIFO от мониторов .NET, но есть ситуации, когда этого не произойдет.

Страница 273 книги гласит: "Поскольку мониторы используют внутренние объекты ядра, они демонстрируют одно и то же грубо-поведение FIFO, которое также проявляет механизмы синхронизации ОС (описанные в предыдущей главе). Мониторы несправедливы, поэтому, если другой поток пробирается и обнаруживает блокировку до того, как пробужденный ожидающий поток попытается получить блокировку, подделанной нити разрешено приобретать блокировку".

Я не могу сразу найти раздел, на который ссылается "в предыдущей главе", но он отмечает, что блокировки были сделаны преднамеренно несправедливыми в последних выпусках Windows, чтобы улучшить масштабируемость и уменьшить конвои блокировки.

Вам определенно нужен ваш замок для FIFO? Возможно, есть другой способ подойти к проблеме. Я не знаю никаких блокировок в .NET, которые гарантированно являются FIFO.

Ответ 3

Вы должны перепроектировать свою систему, чтобы не полагаться на порядок выполнения потоков. Например, вместо того, чтобы ваши потоки вызывают вызов БД, который может занять более одной секунды, попросите свои потоки выполнить команду, которую они выполнили, в структуру данных, такую ​​как очередь (или кучу, если есть что-то, что говорит "это нужно" быть перед другим "). Затем, в свободное время, слейте очередь и сделайте свои вставки db по одному в правильном порядке.

Ответ 4

Нет гарантированного заказа на любые встроенные объекты синхронизации: http://msdn.microsoft.com/en-us/library/ms684266(VS.85).aspx

Если вы хотите получить гарантированный заказ, вам придется самому что-то придумать и что-то построить, обратите внимание, что это не так просто, как может показаться, особенно когда несколько потоков достигают точки синхронизации в (близком к) в то же время. В какой-то мере порядок, в котором они будут выпущены, всегда будет "случайным", поскольку вы не можете предсказать, в каком порядке точка достигнута, так ли это имеет значение?

Ответ 5

На самом деле ответы хорошие, но я решил проблему, удалив таймер и запустив метод (обработчик таймера ранее) в фоновый поток следующим образом

    private void InsertBasicVaraibles()
    {
         int functionStopwatch = 0;
         while(true)
         {

           try
           {
             functionStopwatch = Environment.TickCount;
             DataTablesMutex.WaitOne();//mutex for my shared resources
             //insert into DB 
           }
           catch (Exception ex)
           {
             //Handle            
           }
           finally            
           {                
              DataTablesMutex.ReleaseMutex();
           }

           //simulate the timer tick value
           functionStopwatch = Environment.TickCount - functionStopwatch;
           int diff = INSERTION_PERIOD - functionStopwatch;
           int sleep = diff >= 0 ?  diff:0;
           Thread.Sleep(sleep);
        }
    }

Ответ 6

Следуйте за ответом Мэтью Бриндли.

Если преобразование кода из

lock (LocalConnection.locker) {...}

то вы можете либо сделать IDisposable, либо сделать то, что я сделал:

public static void Locking(Action action) {
  Lock();
  try {
    action();
  } finally {
    Unlock();
  }
}

LocalConnection.Locking( () => {...});

Я решил против IDisposable, потому что он создавал новый невидимый объект для каждого вызова.

Что касается вопроса о возврате, я изменил код:

public sealed class QueuedLock {
    private object innerLock = new object();
    private volatile int ticketsCount = 0;
    private volatile int ticketToRide = 1;
    ThreadLocal<int> reenter = new ThreadLocal<int>();

    public void Enter() {
        reenter.Value++;
        if ( reenter.Value > 1 ) 
            return;
        int myTicket = Interlocked.Increment( ref ticketsCount );
        Monitor.Enter( innerLock );
        while ( true ) {
            if ( myTicket == ticketToRide ) {
                return;
            } else {
                Monitor.Wait( innerLock );
            }
        }
    }

    public void Exit() {
        if ( reenter.Value > 0 ) 
            reenter.Value--;
        if ( reenter.Value > 0 ) 
            return;
        Interlocked.Increment( ref ticketToRide );
        Monitor.PulseAll( innerLock );
        Monitor.Exit( innerLock );
    }
}