Pergunta

Eu tenho um aplicativo FLTK supostamente thread único com um menu pop-up, criado com fluido. Eu tenho uma classe que subclassa fl_gl_window e implementa um método Handle (). O método Handle () chama uma função que cria uma janela pop-up no clique com o botão direito do mouse. Tenho uma longa operação que faço para um dos itens do menu. Meu aplicativo criou um segundo tópico para algum outro propósito. Eu uso bloqueios para proteger algumas seções críticas entre meu thread principal e o segundo thread. Em particular, o dolongoperation () usa os bloqueios.

Meu problema é que eu posso aparecer no menu duas vezes e executar o DOLONGOPERAÇÃO () duas vezes e, em seguida, ele deadlocks consigo mesmo, pendurando o aplicativo. Por que a primeira dolongoperation () não preenche a GUI e me impede de iniciar o dolongoperation () pela segunda vez?

Posso evitar o problema com uma bandeira que uso para desativar o item de menu ofensivo, mas gostaria de entender por que é possível em primeiro lugar.

Aqui está o código, abreviado, é claro. Espero ter incluído todos os bits relevantes.

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.

}
Foi útil?

Solução

Certifique -se de que você não está chamando Fl::wait(...) de mais de um tópico. Estou certo em inferir do seu código que run() executa em seu próprio tópico?

Uma primeira chamada para Fl::wait(), por exemplo, a partir do fio principal, pegaria e processaria o primeiro clique com o botão direito (bloqueando, como esperado, enquanto a primeira chamada para doLongOperation() prossegue); Enquanto isso, as ligações de um segundo fio para por exemplo Fl::wait(timeout)/Fl::check() continuaria a atualizar a tela-e interceptará (e atenderá) o segundo clique com o botão direito, chamando handle() (no segundo fio) enquanto a primeira operação longa ainda está caminhando junto. Isso daria a aparência de um impasse, embora eu espere que a interface do usuário retomasse o redesenho (através do segundo fio) quando as duas operações longas forem concluídas.

Valide o acima, registrando o ID do encadeamento atual dentro popup_menu().

Você deve escolher um único tópico para ligar Fl::wait(...) em um loop, e você não deve bloquear esse loop -gerar quaisquer tarefas não modais ou não UI como encadeamentos separados. Ou seja, quando popup_menu() é chamado, inicie a longa operação em seu próprio tópico; E se popup_menu() é acionado (novamente) enquanto o encadeamento de operação longo ainda estiver em execução, marque o item de menu pop -up como desativado (análogo à sua solução alternativa) ou simplesmente sinalize o thread de operação longo para reiniciar com um novo parâmetro.

Outras dicas

Por qualquer chance, o seu doLongOperation Faça qualquer coisa que possa precisar da bomba de mensagens (ou APCs, algumas API de arquivo do Windows os usam por baixo) para estar em execução (supondo que você veja esse comportamento no Windows)? Por exemplo, se o doLongOperation tenta atualizar a GUI que usa SendMessage Por baixo, você receberá o impasse mesmo em um cenário de thread único.

Além disso, outro tópico tem a seção crítica já reivindicada? Você deve conseguir quebrar o depurador durante o Hang e espero ver quem está esperando o quê.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top