Почему я могу зайти в тупик в однопоточном приложении FLTK? - PullRequest
2 голосов
/ 05 августа 2010

У меня есть якобы однопоточное приложение FLTK с всплывающим меню, созданное с помощью Fluid.У меня есть класс, который подклассы Fl_Gl_Window и реализует метод handle ().Метод handle () вызывает функцию, которая создает всплывающее окно при щелчке правой кнопкой мыши.У меня есть длинная операция, которую я делаю для одного из пунктов меню.Мое приложение создало второй поток для какой-то другой цели.Я использую блокировки для защиты некоторых критических секций между моим основным потоком и этим вторым потоком.В частности, doLongOperation () использует блокировки.

Моя проблема заключается в том, что я могу дважды открыть меню и дважды запустить doLongOperation (), а затем он заблокируется, повесив приложение. Почему первый doLongOperation () не останавливает графический интерфейс и не позволяет мне запустить doLongOperation () во второй раз?

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

Вот код, сокращенно, конечно.Надеюсь, я включил все соответствующие биты.

class MyClass {
  void doLongOperation();
};

class MyApplication : public MyClass {
  MyApplication();
  void run();
  void popup_menu();
};

void MyClass::doLongOperation()
{
   this->enterCriticalSection();
   // stuff
   // EDIT
   // @vladr I did leave out a relevant bit.
   // Inside this critical section, I was calling Fl::check().
   // That let the GUI handle a new popup and dispatch a new
   // doLongOperation() which is what lead to deadlock.
   // END EDIT
   this->leaveCriticalSection();
} 

MyApplication::MyApplication() : MyClass() 
{
  // ...
  { m_mainWindowPtr = new Fl_Double_Window(820, 935, "Title");
    m_mainWindowPtr->callback((Fl_Callback*)cb_m_mainWindowPtr, (void*)(this));
    { m_wireFrameViewPtr = new DerivedFrom_Fl_Gl_Window(10, 40, 800, 560);
      // ...
    }
    m_mainWindowPtr->end();
  } // Fl_Double_Window* m_mainWindowPtr

m_wireFrameViewPtr->setInteractive();

m_mainWindowPtr->position(7,54);
m_mainWindowPtr->show(1, &(argv[0]));

Fl::wait();
}

void MyApplication::run() {
  bool keepRunning = true;
  while(keepRunning) {

  m_wireFrameViewPtr->redraw();
  m_wireFrameView2Ptr->redraw();

  MyClass::Status result = this->runOneIteration();
  switch(result) {
  case DONE: keepRunning = false; break;
  case NONE: Fl::wait(0.001); break;
  case MORE: Fl::check(); break;
  default: keepRunning = false;
  }

}

void MyApplication::popup_menu() {
  Fl_Menu_Item *rclick_menu;


  int longOperationFlag = 0;
  // To avoid the deadlock I can set the flag when I'm "busy".
  //if (this->isBusy()) longOperationFlag = FL_MENU_INACTIVE;

  Fl_Menu_Item single_rclick_menu[] = {
     { "Do long operation", 0, 0, 0, longOperationFlag },
     // etc. ...
     { 0 }
  };

  // Define multiple_rclick_menu...

  if (this->m_selectedLandmarks.size() == 1) rclick_menu = single_rclick_menu;
  else rclick_menu = multiple_rclick_menu;

  const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0);

  if (!m) return;


  if (strcmp(m->label(), "Do long operation") == 0) {
    this->doLongOperation();
    return;
  }

  // Etc.

}

Ответы [ 2 ]

2 голосов
/ 12 августа 2010

Убедитесь, что вы не звоните Fl::wait(...) из более чем одного потока. Правильно ли я понимаю из вашего кода, что run() выполняется в своем собственном потоке?

Aпервый вызов Fl::wait(), например, из основного потока, будет перехватывать и обрабатывать первый щелчок правой кнопкой (блокировка, как и ожидалось, в то время как первый вызов doLongOperation() продолжается);тем временем вызовы второго потока, например, Fl::wait(timeout)/Fl::check() продолжат обновлять отображение - и будут перехватывать (и обслуживать) второй щелчок правой кнопкой мыши, вызывая handle() (во втором потоке), пока первая длинная операциявсе еще тащусь вдоль.Это может привести к возникновению тупика, хотя я ожидаю, что пользовательский интерфейс возобновит перерисовку (через второй поток) после завершения обеих длинных операций.

Проверьте вышеприведенное, зарегистрировав идентификатор текущего потока внутри popup_menu().

Вы должны выбрать один поток для вызова Fl::wait(...) в цикле, и вы не должны блокировать этот цикл - порождает любые немодальные или не-пользовательские задачи как отдельные потоки.Т.е. когда вызывается popup_menu(), запускайте длинную операцию в своем собственном потоке;если popup_menu() запускается (снова), когда поток длинных операций все еще работает, либо пометьте элемент всплывающего меню как отключенный (аналогично вашему решению), либо просто подайте сигнал потоку длинных операций на перезапуск с новым параметром.

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

Может ли ваш doLongOperation сделать что-нибудь, для чего может понадобиться насос сообщений (или APC, файловый API некоторых окон использует их внизу) (если вы видите такое поведение в Windows)?Например, если doLongOperation попытается обновить графический интерфейс, который использует SendMessage, вы получите тупиковую ситуацию даже в однопоточном сценарии.

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

...