ObjectDisposed при закрытии многопоточного приложения

Возможный дубликат:
Как остановить BackgroundWorker на закрытии формы?

** Что касается возможного дублирования - методы BackgroundWorker здесь не применимы.

Ниже моя попытка использовать библиотеку AForge для получения видео с IP-камер.

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

Все работает гладко, пока я не захочу остановить приложение. Строка, помеченная символом ">>>", выдает исключение ObjectDisposed, поэтому мое приложение не заканчивается так гладко, как оно работает.

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

Form1.cs

public void generic_NewFrame(object sender, NewFrameEventArgs e)
{
  ...
  if (pictureBox1.InvokeRequired)
  {                            
>>>   pictureBox1.Invoke(new MethodInvoker(delegate()
                         {                                                       
                           pictureBox1.BackgroundImage = (Image)buf;
                         }));
  }
  else
  {
    pictureBox1.BackgroundImage = (Image)buf;
  }
  ...
}

Как можно короче, класс камеры:

Camera.cs
//Camera thread loop
private void WorkerThread()
{
  while (!stopEvent.WaitOne(0, false))
  {
   ...
     if (!stopEvent.WaitOne(0, false))
     {
       // notify UI thread
       OnNewFrame(new NewFrameEventArgs(Last_frame));
   ...
  }  
}

override public void Play()
{
  stopEvent = new ManualResetEvent(false);

  thread = new Thread(new ThreadStart(WorkerThread));
  thread.Start();
}

override public void Stop()
{
  if (thread != null)
  {
    stopEvent.Set();
  }
}

2 ответа

Я думаю, что проблема заключается в следующем: библиотека вызывает ваш обратный вызов (generic_NewFrame) после закрытия формы. Вы можете исправить это несколькими способами.

Прежде всего вы можете пропустить ваш метод обратного вызова, если ваша форма уже удалена:

public void generic_NewFrame(object sender, NewFrameEventArgs e)
{
  // Lets skip this callback if our form already closed
  **if (this.IsDisposed) return;**

  ...
  if (pictureBox1.InvokeRequired)
  {                            
>>>   pictureBox1.Invoke(new MethodInvoker(delegate()
                         {                                                       
                           pictureBox1.BackgroundImage = (Image)buf;
                         }));
  }
  else
  {
    pictureBox1.BackgroundImage = (Image)buf;
  }
  ...
}

Другой подход заключается в том, чтобы подождать, а не закрывать форму, пока ваша библиотека все еще работает, и ждать в FormClosing или же FormClosed обработчик события:

private void FormClosingEventHandler(object sender, CancelEventArgs e)
{
  // Waiting till your worker thread finishes
   _thread.Join();
}

Или вы можете подождать в вашем методе остановки:

override public void Stop()
{
  if (thread != null)
  {
    stopEvent.Set();
    thread.Join();
  }
}

Чтобы избежать состояния гонки, которое вызывает это, вы можете сделать следующее:

pictureBox1.Invoke(new MethodInvoker(delegate()
                     {                             
                       if (!pictureBox1.IsDisposed) 
                       {                      
                           pictureBox1.BackgroundImage = (Image)buf;
                       }
                     }));

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

Другие вопросы по тегам