Интересно, есть ли лучший способ реализовать этот "простой замок" - PullRequest
2 голосов
/ 24 августа 2010

Есть ли лучший способ реализовать простую блокировку, как показано ниже?

Я хочу перейти на «ДОСОМЕТИНГ», только если он еще не запущен.Должен ли я использовать реал замки здесь?если я использую блокировку, это заставит все стоять в очереди и ждать, пока блокировка не снимется?(это не то, что я хочу!)

Спасибо

  bool running = false;

  void DataDisplayView_Paint(object sender, PaintEventArgs e)
  {
    // if (!this.initialSetDone)  
     if (!running)
     {
        this.running = true;

        //DOSOMETHING

        this.running = false;
     }
 }

Ответы [ 8 ]

5 голосов
/ 24 августа 2010

Нет, вы не хотите использовать здесь замки.Это не проблема синхронизации потоков.Это проблема повторного входа метода.

Вы можете попробовать что-то вроде этого.

bool running = false; 

void DataDisplayView_Paint(object sender, PaintEventArgs e) 
{ 
  if (!this.running)
  {
    this.running = true; 
    try
    {
      //DOSOMETHING 
    }
    finally
    {
      this.running = false; 
    }
  }
}
2 голосов
/ 24 августа 2010

Вам просто нужно синхронизировать (это самый простой способ) биты кода:

bool running = false;
readonly object padlock = new object();

  void DataDisplayView_Paint(object sender, PaintEventArgs e)
  {

     if (!this.initialSetDone)
     {
        lock(padlock)
        {
          if(running) return;
          running = true;
        }
        try {

          //DOSOMETHING
        }
        finally
        {
          lock(padlock)
          {
            this.running = false;
          }
        }
     }
 }
1 голос
/ 24 августа 2010

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

1 голос
/ 24 августа 2010

Я что-то упустил? Код, который вы опубликовали, похоже, ничего не делает. То есть код будет выполняться независимо от того, истинно ли running.

Как правило, любой код, который пытается "заблокировать" себя вот так ...

if (!running)
{
    running = true;

    try
    {
        // This code should not call itself recursively.
        // However, it may execute simultaneously on more than one thread
        // in very rare cases.
    }
    finally
    {
        running = false;
    }
}

... это прекрасно, если вы находитесь в однопоточном сценарии. Если вы используете многопоточный код, могут возникнуть проблемы, поскольку вы предполагаете, что ни один из двух потоков не достигнет строки if (!running) одновременно.

Решение в многопоточном коде заключается в использовании некоторого вида атомарного коммутатора. Я использовал AutoResetEvent для этой цели:

var ready = new AutoResetEvent(true);

if (ready.WaitOne(0))
{
    try
    {
        // This code will never be running on more than one thread
        // at a time.
    }
    finally
    {
        ready.Set();
    }
}
1 голос
/ 24 августа 2010

Лучший способ - использовать блок try / finally

try { 
  this.running = true;
  ...
} finally {
  this.running = false;
}

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

0 голосов
/ 25 августа 2010

Я хочу перейти на «ДОСОМЕТИНГ» только в том случае, если он еще не запущен

Ваш вопрос не имеет достаточной информации, поэтому я не могу не сделать предположения о вашейкод.

  • Мое первое предположение состоит в том, что, основываясь на подписи DataDisplayView_Paint(object s, PaintEventArgs e), ваш код выполняется в потоке графического интерфейса.

  • Мой второйПредполагается, что ваш код DOSOMETHING является синхронным.

Имея это в виду, вот версия вашего кода, которая гарантирует, что мы будем запускать DOSOMETHING, только если он еще не запущен:

void DataDisplayView_Paint(object s, PaintEventArgs e)
{
    //DOSOMETHING
}

Поток GUI будет обрабатывать только одно сообщение за раз, а ваш метод DataDisplayView_Paint не завершится, пока не завершится DOSOMETHING.Если вы делаете что-то с графическим интерфейсом, например, рисуете объект Graphics или меняете метки, то этот код не будет вызываться из более чем одного потока - и если это произойдет, .NET выдаст исключение.Другими словами, вам не нужно никакой синхронизации.


Предположим, что DOSOMETHING работает асинхронно - теперь у нас есть интересная проблема, но ее очень легко решить,и вам не нужно никаких bools.

По сути, все, что вы делаете, это отключаете ваш обработчик событий во время выполнения DOSOMETHING, а затем снова его включаете.Вместо того, чтобы использовать bool, отцепите и повторно подключите ваш обработчик событий по мере необходимости:

void DataDisplayView_Paint(object s, PaintEventArgs e)
{
    DataDisplayView.Paint -= DataDisplayView_Paint;
    DoSomethingAsynchronously(); // re-hooks event handler when completed
}

void DoSomethingAsychronously()
{
    ThreadPool.QueueUserWorkItem(() =>
    {
        try
        {
            // DOSOMETHING
        }
        finally
        {
            // may need a lock around this statement
            DataDisplayView.Paint += DataDisplayView_Paint;
        }
    });
}
0 голосов
/ 24 августа 2010

Вы меняете переменные имена в середине, поэтому я предполагаю, что вы хотели:

  bool running = false; 

  void DataDisplayView_Paint(object sender, PaintEventArgs e) 
  { 
     if (!this.running) 
     { 
        this.running = true; 

        //DOSOMETHING 

        this.running = false; 
     } 
 }

Проблема, с которой вы здесь сталкиваетесь, заключается в том, что если DataDisplayView_Paint можно вызывать из нескольких потоков, то возможно, что между if (!this.running) и this.running = true; другой поток сможет подключиться и запустить DOSOMETHING (потому что выполняется все еще false) , Затем возобновится первый поток и снова запустите DOSOMETHING. Если это возможно, вам нужно использовать реальную блокировку.

0 голосов
/ 24 августа 2010

Если вместо этого вы используете Monitor.TryEnter, вы можете указать время ожидания, и в этом случае вы получите такой результат:

  • только один поток может запустить DOSOMETHING одновременно
  • последующие вызовы попытаются получить блокировку и отказаться после истечения срока ожидания

Если вы не предоставите тайм-аут или не установите тайм-аут на 0, этот вызов не будет блокироваться и сразу же вернется (возможно, это больше соответствует вашим требованиям?):

if (!this.initialSetDone && Monitor.TryEnter(_lock))
{
   // DOSOMETHING
}

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

private volatile bool running;

if (!this.initialSetDone && !this.running)  // #1
{
   this.running = true;
   try
   {
     // DOSOMETHING
   }
   finally
   {
     this.running = false;
   }
}

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

...