Как сделать метод эксклюзивным в многопоточном контексте? - PullRequest
3 голосов
/ 29 сентября 2008

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

Давайте рассмотрим пример:

  1. Таймер истекает, поэтому метод называется. Задача может занять несколько секунд.

  2. Сразу после этого пользователь нажимает на некоторые кнопка, которая должна вызвать та же задача: БАМ. Ничего не делает поскольку метод уже запущен.

Я использовал следующее решение:

public void DoRecurentJob()
{
    if(!Monitor.TryEnter(this.lockObject))
    {
        return;
    }

    try
    {
        // Do work
    }
    finally 
    {
        Monitor.Exit(this.lockObject);
    }
}

Где lockObject объявлено так:

private readonly object lockObject = new object();

Редактировать : Будет только один экземпляр объекта, который содержит этот метод, поэтому я обновил объект блокировки, чтобы он был нестатичным.

Есть ли лучший способ сделать это? Или, может быть, это просто неправильно по какой-то причине?

Ответы [ 8 ]

4 голосов
/ 29 сентября 2008

Это выглядит разумно, если вы просто заинтересованы в том, чтобы метод не выполнялся параллельно. Ничто не мешает ему запускаться сразу после друг друга, скажем, вы нажали кнопку через полсекунды после того, как таймер выполнил Monitor.Exit ().

Имеет смысл также иметь объект блокировки как статический только для чтения.

2 голосов
/ 29 сентября 2008

Я думаю, что Microsoft рекомендует , используя оператор lock вместо непосредственного использования класса Monitor. Это обеспечивает более чистую компоновку и обеспечивает снятие блокировки при любых обстоятельствах.

public class MyClass
{

  // Used as a lock context
  private readonly object myLock = new object();

  public void DoSomeWork()
  {
    lock (myLock)
    {
      // Critical code section
    }
  }
}

Если вашему приложению требуется блокировка для охвата всех экземпляров MyClass, вы можете определить контекст блокировки как статическое поле:

private static readonly object myLock = new object();
2 голосов
/ 29 сентября 2008

Minor nit: если переменная lockObject является статической, «this.lockObject» не должен компилироваться. Также кажется немного странным (и, по крайней мере, должно быть задокументировано), что, хотя это метод экземпляра, он также имеет отчетливое поведение для всего типа. Возможно, сделать это статическим методом, который принимает экземпляр в качестве параметра?

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

Я думаю, что это должно работать, но это немного странно. Я вообще не фанат использования ручной блокировки, просто потому, что так легко ошибиться - но это выглядит нормально. (Необходимо учитывать асинхронные исключения между «если» и «попытаться», но я подозреваю, что они не будут проблемой - я не могу вспомнить точные гарантии, сделанные CLR.)

2 голосов
/ 29 сентября 2008

Вы также можете использовать Mutex или Semaphore, если хотите, чтобы он работал кросс-процесс (с небольшим снижением производительности), или если вам нужно установить любое другое число, кроме одного из разрешенных одновременных потоков, выполняющих ваш фрагмент кода .

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

1 голос
/ 29 сентября 2008

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

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

public class MyClass
{ 
    public void AccessResource()
    {
        OneAtATime(this);
    }

    private static void OneAtATime(MyClass instance) 
    { 
       if( !Monitor.TryEnter(lockObject) )
       // ...
0 голосов
/ 31 мая 2017

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

https://codereview.stackexchange.com/questions/16150/singleton-task-running-using-tasks-await-peer-review-challenge

private queued = false;
private running = false;
private object thislock = new object();

void Enqueue() {
    queued = true;
    while (Dequeue()) {
        try {
            // do work
        } finally {
            running = false;
        }
    }
}

bool Dequeue() {
    lock (thislock) {
        if (running || !queued) {
            return false;
        }
        else
        {
            queued = false;
            running = true;
            return true;
        }
    }
}
0 голосов
/ 01 октября 2008

Более декларативный способ сделать это - использовать спецификатор MethodImplOptions.Synchronized для метода, к которому вы хотите синхронизировать доступ:

[MethodImpl(MethodImplOptions.Synchronized)] 
public void OneAtATime() { }

Однако этот метод не рекомендуется по нескольким причинам, большинство из которых можно найти здесь и здесь . Я публикую это, чтобы вы не испытывали соблазна использовать его. В Java synchronized является ключевым словом, поэтому оно может появляться при просмотре шаблонов потоков.

0 голосов
/ 29 сентября 2008

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

Я обычно стараюсь сделать все ресурсы, которые пытаюсь защитить, от доступа к ним несколькими частными переменными экземпляра класса, а затем блокировать их как частную переменную экземпляра. Таким образом, вы можете создавать экземпляры нескольких объектов, если вам нужно масштабировать.

...