Потокобезопасное выполнение с использованием System.Threading.Timer и Monitor - PullRequest
5 голосов
/ 26 февраля 2011

Использование System.Threading.Timer приводит к тому, что потоки запускаются из ThreadPool, что означает, что, если интервал выполнения для таймера истекает, пока поток все еще обрабатывает в порядке предыдущего запроса, то тот же обратный вызов будет делегирован выполнить в другом потоке. Это, очевидно, вызовет проблемы в большинстве случаев, если обратный вызов не распознается вновь входящим, но мне интересно как сделать это лучшим (то есть безопасным) способом.

Допустим, у нас есть следующее:

ReaderWriterLockSlim OneAtATimeLocker = new ReaderWriterLockSlim();

OneAtATimeCallback = new TimerCallback(OnOneAtATimeTimerElapsed);
OneAtATimeTimer = new Timer(OneAtATimeCallback , null, 0, 1000);

Если весь Шебанг будет заблокирован, как таковой:

private void OnOneAtATimeTimerElapsed(object state)
{
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {
        //get real busy for two seconds or more

        OneAtATimeLocker.ExitWriteLock();
    }
}

Или, должен управляться только вход, и исключить «нарушителей», как таковые:

private void OnOneAtATimeTimerElapsed(object state)
{
    if (!RestrictOneAtATime())
    {
        return;
    }

    //get real busy for two seconds or more

    if(!ReleaseOneAtATime())
    {
        //Well, Hell's bells and buckets of blood!
    }       
}

bool OneAtATimeInProgress = false;

private bool RestrictToOneAtATime()
{
    bool result = false;
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {
        if(!OneAtATimeInProgress)
        {
            OneAtATimeInProgress = true;
            result = true;
        }
        OneAtATimeLocker.ExitWriteLock();
    }
    return result;
}

private bool ReleaseOneAtATime()
{
    bool result = false;
    //there shouldn't be any 'trying' about it...
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {            
        if(OneAtATimeInProgress)
        {
            OneAtATimeInProgress = false;
            result = true;
        }
        OneAtATimeLocker.ExitWriteLock();
    }
    return result;
}

Имеет ли первое влияние на производительность, поскольку оно блокируется для степени метода?

Может ли второй обеспечить безопасность, о которой можно подумать - это что-то ускользает от меня?

Есть ли другие способы сделать это надежно и желательно?

Ответы [ 2 ]

10 голосов
/ 26 февраля 2011

Множество способов справиться с этим. Простой способ - просто не делать таймер периодическим, сделать его одним выстрелом, только установив аргумент dueTime . Затем повторно включите таймер в обратном вызове в блоке finally. Это гарантирует, что обратный вызов не может быть запущен одновременно.

Это, конечно, делает интервал переменным по времени выполнения обратного вызова. Если это нежелательно, и обратный вызов только иногда занимает больше времени, чем период таймера, тогда простая блокировка выполнит работу. Еще одна стратегия - Monitor.TryEnter и просто отказываются от обратного вызова, если он возвращает false. Ни один из них не особенно хорош, выберите то, что вам больше нравится.

0 голосов
/ 26 февраля 2011

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

Кроме того, если вашОбработчик тиков регулярно занимает больше времени, чем интервал таймера, тогда либо ваш интервал таймера слишком короткий, либо ваш оптимизатор должен быть оптимизирован.И вы рискуете получить огромное количество задержек для обработки тиков, что означает, что любой статус, который вы обновляете, несколько задерживается.

Если вы решили выбросить перекрывающиеся тики (то есть вызовы, поступающие во время предыдущеготик обрабатывается), я бы порекомендовал использовать однократный таймер, который вы сбрасываете каждый раз после обработки.То есть, вместо того, чтобы отбрасывать тики, невозможно наложение тиков.

Есть ли конкретная причина, по которой вы используете ReaderWriterLockSlim.TryEnterWriteLock вместо Monitor.TryEnter?

Кроме того, если выВы собираетесь использовать ReaderWriterLockSlim или Monitor, вы должны защитить их с помощью try...finally.То есть, используя Monitor:

if (myLock.TryEnter(0))
{
    try
    {
        // process
    }
    finally
    {
        myLock.Exit();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...