Сигнально-слотовые соединения


Итак, мы можем взять сигнал объекта одного потока и соединить его со слотом объекта другого потока. Как мы уже знаем, соединение с помощью метода connect() предоставляет дополнительный параметр, обозначающий режим обработки и равный, по умолчанию, значению Qt::AutoConnection, которое соответствует автоматическому режиму. Как только происходит высылка сигнала, Qt проверяет — происходит связь в одном и том же или разных потоках. Если это один и тот же поток, то высылка сигнала приведет к прямому вызову метода. В том случае, если это разные потоки, сигнал будет преобразован в событие и доставлен нужному объекту.

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

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

{рисунок}


class MyThread : public QThread {
Q_OBJECT
private:
    int m_nValue;

public:
    MyThread() : m_nValue(10)
    {
    }

    void run()
    {
        QTimer timer;
        connect(&timer, SIGNAL(timeout()), SLOT(slotNextValue()));
        timer.start(1000);

        exec();
    }

signals:
    void finished    (   );
    void currentValue(int);

public slots:
    void slotNextValue()
    {
        emit currentValue(--m_nValue);

        if (!m_nValue) {
            emit finished();
        }
        
    }
};

Класс MyThread представляет собой класс для управления потоком. Он должен быть унаследован от класса QThread. Обратите внимание, что в определении нашего класса потока указан макрос Q_OBJECT, что необходимо для использования сигналов и слотов. Конструктор нашего класса пуст и служит только для инициализации атрибута m_nValue. Одна из самых интересных частей нашего класса — это метод run(), который должен быть переопределен — поместим в него код, выполняющийся в потоке. В этом методе мы создаем объект таймера и соединяем его сигнал timeout() со слотом slotNextValue(), запускаем таймер с интервалом в одну секунду и приводим в действие цикл обработки событий вызовом метода exec().

Вас не должно смущать то, что объект таймера (объект timer) был создан статически, а не динамически с помощью оператора new, так как метод run() является методом для исполнения кода в потоке и его разрушение произойдет только при завершении работы потока. После вызова метода exec() произойдет запуск цикла событий, который произведет блокировку исполнения всех дальнейших команд метода run(), если бы таковые имелись. Этот метод можно образно сравнить с функцией main(), ведь в ней мы поступаем аналогичным образом, когда реализуем основной поток приложения, без которого не была бы возможна работа ни одного Qt-приложения с пользовательским интерфейсом.

Слот slotNextValue() уменьшает значение атрибута m_nValue на единицу и высылает сигнал currentValue() с его актуальным значением. Если значение станет нулевым, то будет произведена высылка сигнала finished(), информирующая о конце работы.


int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QLCDNumber   lcd;
    MyThread     thread;

    QObject::connect(&thread, SIGNAL(currentValue(int)), 
                     &lcd,    SLOT(display(int))
                    );
    QObject::connect(&thread, SIGNAL(finished()), 
                     &app,    SLOT(quit())
                    );

    lcd.setSegmentStyle(QLCDNumber::Filled);
    lcd.display(10);
    lcd.resize(220, 90);
    lcd.show();
    thread.start();
 
    return app.exec();
}

#include "main.moc"

В основной программе, мы создаем виджет электронного индикатора lcd и объект потока thread. Для отображения значений, высылаемых потоком, мы соединяем сигнал currentValue() со слотом виджета индикатора display(). Также мы соединяем сигнал finished() объекта потока со слотом приложения quit() для того, чтобы приложение завершило свою работу. После некоторых настроек, влияющих на показ виджета электронного индикатора, мы запускаем поток методом start() . Ввиду того, что весь исходный код у нас реализован в одном файле main.cpp, нам необходимо включить метаинформацию, которая будет сгенерирована МОС, при помощи директивы include.

В только что продемонстрированном примере, слоты нашего потока не соединяются с объектами другого потока, и, в этом случае, производится прямая высылка сигналов без внутреннего генерирования событий. Теперь давайте изменим наш пример таким образом, чтобы слот нашего потока был соединен с сигналом из другого потока. Для этого просто уберите три строки, связанные с таймером, из метода run() и перенесите их в реализацию основного потока (в функцию main()). Метод run() должен выглядеть следующим образом:


void run()
{ 
    exec();
}

А в функцию main() добавьте строки:


QTimer timer; 
QObject::connect(&timer, SIGNAL(timeout О), &thread, SLOT(slotNextValue())); 
timer.start(1000);

Теперь наш поток соединен с объектом таймера, созданным в основном потоке, и отсылка сигналов должна приводить к внутренней генерации событий. Откомпилируйте и запустите пример. Вы убедитесь в том, что он работает так же, как и его предшественник.

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

{рисунок}


class MyThread : public QThread {
Q_OBJECT

public:
    void run()
    {
        for (int i = 0; i <= 100; ++i) {
            usleep(100000);       
            emit progress(i);
        }
    }

signals:
    void progress(int);
};

В основном методе нашего потока run() мы запускаем цикл от 0 до 100, в котором через каждые 10 миллисекунд высылается сигнал progress() с актуальным значением переменной цикла. Остановка выполнения цикла на десять миллисекунд производится при помощи метода QThread::usleep().


int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    QProgressBar prb;
    MyThread     thread;

    QObject::connect(&thread, SIGNAL(progress(int)), 
                     &prb,    SLOT(setValue(int))
                    );

    prb.show();

    thread.start();
 
    return app.exec();
}

#include "main.moc"

После создания виджета прогресса производится создание одного потока — thread, а его сигнал progress() соединяется, для отображения высылаемых им значений, со слотом setValue() виджета индикатора прогресса. Запуск потока приводится в действие методом start().

Читать далее: Связь между потоками с помощью высылки событий