Избегайте застревания обратного вызова - PullRequest
2 голосов
/ 08 марта 2010

Это вопрос общего дизайна приложений, управляемых событиями c ++.
Предположим, что у нас есть два потока: «Диспетчер» (или «Механизм» ...) и «Слушатель» (или «Клиент» ...).
Давайте предположим, что я пишу код Dispatcher и выпускаю его как библиотеку. Конечно, я также пишу интерфейс Listener.
Когда Dispatcher выполняется (после регистрации прослушивателя)

listenerInstance.onSomeEvent();

код обработки события будет фактически выполняться потоком Dispatcher, поэтому, если человек, реализующий Listener, напишет что-то вроде

void Listener::onSomeEvent() { while(true) ; }

Диспетчер застрянет навсегда.

Существует ли "простой старый c ++" (я имею в виду не boost или libsigc ++) способ "разъединить" два класса, поэтому я могу быть уверен, что мой Dispatcher будет работать нормально, независимо от того, что Listeners делает в обратных вызовах?

пока и спасибо заранее,
Andrea

Ответы [ 4 ]

4 голосов
/ 08 марта 2010

Что ж, если событие вызывается в том же потоке (как я понимаю, это может быть требованием), тогда вы ничего не можете с этим поделать.

Если это приложение Win32 с насосом сообщений, вы можете зарегистрировать сообщение Windows и вызвать PostMessage с данными, представляющими это событие, и вы можете залатать цикл сообщений, чтобы интерпретировать это сообщение и вызвать событие. То, что вы получаете, - это своего рода разъединение, вызов события является асинхронным (т.е. вызов события будет возвращаться независимо от того, что). Но позже, когда вы обработаете свои сообщения и фактически вызовете событие, ваш основной поток все равно будет остановлен, и больше ничего не будет работать, пока не будет готов обработчик события.

Другой альтернативой является просто создание нового потока (или использование пула потоков) для вашего вызова. Это не будет работать для событий, которые требуют определенного потока (т.е. обновления потоков пользовательского интерфейса). Кроме того, это добавляет накладные расходы на синхронизацию и порождающие потоки, и вы можете лишить систему потоков и / или времени процессора.

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

3 голосов
/ 08 марта 2010

Боюсь, что нет никакого родного C ++ способа сделать это. Для окон вы можете использовать асинхронные вызовы процедур (APC)

2 голосов
/ 08 марта 2010

Одним из подходов может быть вызов onSomeEvent в выделенный поток. Это не 100% пуленепробиваемое, но это позволит избежать проблемы while(true);.

Надеюсь, это поможет

1 голос
/ 08 марта 2010

Существует pure C++ способ добиться того, что вы упоминаете. Однако это очень неэффективно. Вот образец:

class Listener
{
  bool myHasEvent;

private:
  void ProcessEvent()
  {
    while (true)
    {
      if (!myHasEvent)
        continue;           //spin lock

      // Do real processing
      myHasEvent = false;
    }
  }

public:
  void onSomeEvent() { myHasEvent = true; }
};

Однако я бы рекомендовал против такого подхода. Вместо этого я бы преобразовал это в более специфичный для платформы код. Я бы заменил спин-блокировку if (!myHasEvent) continue; на специфичную для ОС подпрограмму ожидания (т.е. WaitForSingleObject на Win32), передавшую дескриптор события . Затем в onSomeEvent вместо myHasEvent = true; я бы установил событие в сигнальное состояние (т.е. SetEvent на Win32). Это было бы намного эффективнее, потому что поток не будет тратить процессорное время во время ожидания.

Другим методом является PostMessage, как предлагает Blindly .

...