Знание, когда цикл событий QThread запущен из другого потока. - PullRequest
2 голосов
/ 10 марта 2011

В моей программе я создаю подкласс QThread, и я реализовал виртуальный метод run() примерно так:

void ManagerThread::run() {

    // do a bunch of stuff,
    // create some objects that should be handled by this thread
    // connect a few signals/slots on the objects using QueuedConnection

    this->exec(); // start event loop
}

Теперь, в другом потоке (назовем его MainThread), я запускаюManagerThread и ждут его started() сигнала , после чего я перехожу к использованию сигналов и слотов, которые должны обрабатываться ManagerThread.Однако сигнал started() по существу излучается непосредственно перед вызовом run(), поэтому в зависимости от планирования потоков я теряю некоторые сигналы из MainThread, поскольку цикл обработки событий еще не начался!

( РЕДАКТИРОВАТЬ: оказывается, что это не проблема, просто сигналы не связаны во времени, но по той же причине)

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

Есть ли какой-то определенный / простой способ узнать, что цикл событий начался?

Спасибо!

EDIT2: (РЕШЕНИЕ)

Хорошо, так получается, что проблема не совсем в том, что я сформулировал.Тот факт, что цикл обработки событий не начался, не является проблемой, поскольку сигналы должны ставиться в очередь до тех пор, пока он не запустится.Проблема в том, что некоторые сигналы не будут подключены вовремя для вызова, поскольку сигнал started() испускается до вызова run().

Решение состоит в для передачи другого пользовательского сигнала после всех подключений и непосредственно перед exec.Таким образом гарантируется подключение всех сигналов / слотов.

Это решение проблемы my , но на самом деле это не ответ на заголовок темы.Я принял ответ, что отвечает на заголовок.

Я оставил весь свой код ниже для тех, кому любопытно, с решением, ждать другого сигнала в методе instance().

КОД:

Многие из вас говорят, что я не могу потерять сигналы, так что вот моя реализация всего класса.Я упросту его до самого необходимого.

Вот интерфейс ManagerThread:

// singleton class
class ManagerThread: public QThread {

    Q_OBJECT

    // trivial private constructor/destructor

    public:
    static ManagerThread* instance();

    // called from another thread
    public:
    void doSomething(QString const& text);

    // emitted by doSomething,
    // connected to JobHandler whose affinity is this thread.
    signals:
    void requestSomething(QString const& text);

    // reimplemented virtual functions of QThread
    public:
    void run();

    private:
    static QMutex s_creationMutex;
    static ManagerThread* s_instance;
    JobHandler* m_handler; // actually handles the requests
};

Некоторые соответствующие реализации.Создание единственного экземпляра потока:

ManagerThread* ManagerThread::instance() {
    QMutexLocker locker(&s_creationMutex);
    if (!s_instance) {
        // start socket manager thread, and wait for it to finish starting
        s_instance = new ManagerThread();

        // SignalWaiter essentially does what is outlined here:
        // /3234007/v-ozhidanii-signala
        SignalWaiter waiter(s_instance, SIGNAL(started()));
        s_instance->start(QThread::LowPriority);
        qDebug() << "Waiting for ManagerThread to start";
        waiter.wait();
        qDebug() << "Finished waiting for ManagerThread thread to start.";
    }

    return s_instance;
}

Повторная реализация прогона, который устанавливает сигналы / слоты и запускает цикл обработки событий:

void ManagerThread::run() {
   // we are now in the ManagerThread thread, so create the handler
   m_handler = new JobHandler();

   // connect signals/slots
   QObject::connect(this,
                    SIGNAL(requestSomething(QString const&)),
                    m_handler,
                    SLOT(handleSomething(QString const&)),
                    Qt::QueuedConnection);

   qDebug() << "Starting Event Loop in ManagerThread";

   // SOLUTION: Emit signal here and wait for this one instead of started()

   this->exec(); // start event loop
}

Функция, которая делегирует обработку правильному потоку.Вот где я посылаю потерянный сигнал:

void ManagerThread::doSomething(QString const& text) {

    qDebug() << "ManagerThread attempting to do something";

    // if calling from another thread, have to emit signal
    if (QThread::currentThread() != this) {
        // I put this sleep here to demonstrate the problem
        // If it is removed there is a large chance the event loop
        // will not start up in time to handle the subsequent signal
        QThread::msleep(2000);  
        emit(requestSomething(text));
    } else {
       // just call directly if we are already in the correct thread
       m_handler->handleSomething(text);
    }
}

Наконец, вот код из MainThread, который завершится ошибкой, если цикл событий не запустится вовремя:

ManagerThread::instance()->doSomething("BLAM!");

Предполагая, что обработчик просто распечатывает свой текст, вот что распечатывается при успешном запуске:

Ожидание запуска ManagerThread
Завершено ожидание запуска потока ManagerThread.
Запуск цикла событий в ManagerThread
ManagerThread пытается что-то сделать
BLAM!

И вот что происходит при неудачном запуске:

ОжиданиеManagerThread для запуска
Завершено ожидание запуска потока ManagerThread.
ManagerThread пытается что-то сделать
Запуск цикла событий в ManagerThread

Ясно, что цикл событий начался после того, как сигнал был отправлен,и БЛЕМ никогда не печатает.Здесь есть условие гонки, которое требует знания того, когда начинается цикл событий, чтобы исправить это.

Может быть, я что-то упускаю, а проблема в чем-то другом ...

Большое спасибо, если вы все это прочитали!Уф!

Ответы [ 4 ]

3 голосов
/ 10 марта 2011

Если вы правильно настроили соединения, вы не должны терять сигналы. Но если вы действительно хотите получить уведомление о начале цикла событий потока, вы можете попробовать QTimer::singleShot() в вашем run() прямо перед вызовом exec(). Он будет доставлен при запуске цикла событий и будет доставлен только один раз.

1 голос
/ 10 марта 2011

Это не проблема. Сигналы между потоками ставятся в очередь (более конкретно, вам нужно настроить их для постановки в очередь в вызове connect(), поскольку прямые соединения между потоками небезопасны).

http://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads

1 голос
/ 10 марта 2011

Вы можете посмотреть на QSemaphore для сигнализации между потоками.Слоты и сигналы лучше подходят для событий пользовательского интерфейса и обратных вызовов в одном потоке.

Редактировать: В качестве альтернативы вы можете комбинировать QMutex с QWaitCondition, если семафор не применим.Будет полезен другой пример кода, чтобы увидеть, как вы используете ManagerThread в сочетании с MainThread.

0 голосов
/ 10 марта 2011

Вы можете создать соединения сигнал / слоты в конструкторе ManagerThread.Таким образом, они, безусловно, связаны еще до вызова run ().

...