Управление QObject, работающим в другом потоке, в обход очереди событий, замораживает графический интерфейс

Я работаю с QThread и механизмом слотов/сигналов; Я знаю, что есть много тем по этому поводу в Интернете в целом и здесь, в SO, в частности, но я так и не смог найти решение. Во всяком случае, вот контекст.

Фрагмент кода, который я пытаюсь придумать, предназначен для управления через графический интерфейс длительным процессом, поэтому используется QThread.

У меня есть окно с двумя кнопками, старт и стоп. У моего Window также есть QThread и Task, где последний наследуется от QObject. Я хочу иметь возможность остановить свою задачу во время ее выполнения и предотвратить ее повторный запуск, если нажата кнопка «Пуск», когда она уже запущена.

Вот отрывок из Task (который имитирует долгий процесс):

class Task: public QObject
{
public:

  Task(): QObject(), stop_(true) {}

private slots:

  void startTask()
  {
    stop_ = false;
    run();
  }

  void stopTask()
  {
    stop_ = true;
  }

  void run() const
  {
    while ( ! stop_)
    {
      sleep(1);
    }
  }

  bool stop_;
};

Я сделал две связи между кнопками и задачей в конструкторе моего Window:

class Window: public QWidget
{
public:

  Window()
  {
    // Instantiate buttons and put them in a layout.
    // ...

    connect(buttonStart_, SIGNAL(clicked()), &task_, SLOT(startTask()));
    connect(buttonStop_, SIGNAL(clicked()), &task_, SLOT(stopTask()),
            Qt::DirectConnection);

    task_.moveToThread(&thread);
    thread_.start();
  }

private:
  QPushButton buttonStart_;
  QPushButton buttonStop_;

  QThread thread_;
  Task task_;
};

Я использовал Qt::DirectConnection во втором connect() для того, чтобы "форсировать" обработку моего сигнала с запросом на остановку задачи, т.к. (как я понимаю) task_ нужно вернуться из своей работы перед дальнейшей обработкой событий (если я использую подключение по умолчанию, все мои клики обрабатываются после того, как моя задача «выполнена»).

Здесь Qt::DirectConnection «обходит» очередь событий, и таким образом я могу остановить свою задачу. Но, честно говоря, я не знаю, правильный ли это способ сделать это или это обходной путь (поэтому, возможно, корень моей проблемы).

В любом случае, это работает нормально, но когда я начинаю играть с моими кнопками, графический интерфейс в конечном итоге зависает, и это моя проблема!

Любая помощь приветствуется; Спасибо за ваше время!


person piwi    schedule 03.02.2012    source источник


Ответы (2)


Использование DirectConnection означает, что слоты будут выполняться в потоке пользовательского интерфейса (пока ваш поток задач все еще выполняется). Это не то, что вы хотите. Способ справиться с этим - использовать цикл событий в вашем потоке, запустив QThread::exec().

Чтобы ваш поток мог отвечать так, как вы хотите, вам необходимо убедиться, что поток способен обрабатывать входящие события. Есть несколько способов справиться с этим. Можно было бы время от времени вызывать QCoreApplication::processEvents() во время выполнения вашей задачи. Другой вариант — использовать QTimer, подключенный к слоту, который выполняет некоторую обработку. Важно убедиться, что цикл событий в вашем потоке может работать.

person Dan Milburn    schedule 03.02.2012
comment
Вы имеете в виду, что мой метод Task::run() не позволяет моему потоку обрабатывать что-либо, даже сигналы? Из вашего ответа я понимаю, что мой цикл while должен регулярно вызывать processEvents() (или аналогичный). Я прав? - person piwi; 03.02.2012
comment
Да, если ваша задача выполняется внутри потока, поток не может делать ничего другого, пока вы не скажете ему об этом. Вызов processEvents() в вашем цикле while должен работать (если поток запущен с помощью exec()). ETA: Что-то, что я только что заметил, QThread::run() по умолчанию уже вызывает exec(), так что эта часть будет работать. Просто избавьтесь от DirectConnection и вызовите processEvents() в цикле run() вашей задачи. - person Dan Milburn; 03.02.2012
comment
Работает как шарм. Большое спасибо! - person piwi; 06.02.2012

Первое, что вам нужно понять, это то, в каком потоке выполняются ваши соединения сигнала/слота.

Поведение по умолчанию в этом конкретном случае заключается в использовании Qt::QueuedConnection, что поместит ваше сигнальное событие в очередь событий принимающего потока. В случае Qt::DirectConnection слот выполняется в потоке объекта, отправившего сигнал.

Использование Qt::DirectConnection будет безопасным только в этом случае, если вы поместите мьютекс вокруг своей переменной stop_, чтобы предотвратить одновременный доступ к ней обоих потоков.

person Chris    schedule 03.02.2012
comment
На самом деле, я попытался поместить мьютекс вокруг моей переменной stop_, но это не решило проблему (что я сделал: Task получает член QMutex, а манипуляция stop_ окружена вызовами lock() и unlock()). Но использование Qt::QueuedConnection заставит принимающий поток (тот, который предназначен для моей задачи) обрабатывать мое событие только после того, как моя задача будет завершена. Я хочу обработать его, пока он работает. - person piwi; 03.02.2012