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


Высылка событий — это еще одна из возможностей для осуществления связи между объектами. Как мы знаем, есть два метода для высылки событий: QCoreApplication::postEvent() и QCoreApplication::sendEvent(). Здесь есть небольшой нюанс, который нужно знать: высылка событий методом postEvent() обладает надежностью в потоках, а при помощи метода sendEvent() — нет. Поэтому при работе с разными потоками всегда используйте метод postEvent(). На рисунке показано, как с помощью механизма обмена событиями разных потоков можно осуществлять связь между двумя потоками. Поток может высылать события другому потоку, который, в свою очередь, может ответить другим событием и т. д. Сами же события, обрабатываемые циклами событий потоков, будут принадлежать тем потокам, в которых они были созданы.

{рисунок}

Для того чтобы объект потока был в состоянии обрабатывать получаемые события, в классе потока нужно реализовать метод QObject::event().

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


class ProgressEvent : public QEvent {
private:
    int m_nValue;

public:
    enum {ProgressType = User + 1};

    ProgressEvent() : QEvent((Type)ProgressType)
    {
    }

    void setValue(int n) 
    {
        m_nValue = n;
    }
 
    int value() const
    {
        return m_nValue;
    }
};

Первое, что нам нужно сделать, — это создать класс для нашего события, которое мы будем высылать из потока. Наш класс наследуется от класса QEvent и определяет атрибут целого типа m_nValue. Для получения и установки значений этого атрибута класс содержит метод value() и setValue(). В конструкторе мы задаем тип нашего события, передавая его целочисленный идентификатор в конструктор класса QEvent().


class MyThread : public QThread {
private:
    QObject* m_pobjReceiver;

public:
    MyThread(QObject* pobjReceiver) : m_pobjReceiver(pobjReceiver)
    {
    }

    void run()
    {
        for (int i = 0; i <= 100; ++i) {
            usleep(100000);       

            ProgressEvent* pe = new ProgressEvent;
            pe->setValue(i);
            QApplication::postEvent(m_pobjReceiver, pe);
        }
    }
};

Определяем в классе атрибут, указывающий на объект-получатель нашего события, создаем объект класса события и высылаем его объекту-получателю с помощью метода postEvent(). Создание нашего события в методе run() очень похоже на утечку памяти (memory leak), но ею не является, так как после обработки все объекты событий удаляются.

Примечание. Очевидно, что если бы нам понадобилось выслать событие еще одному объекту, то нам пришлось бы создать второй объект класса и повторить действия, проделанные для первого события. Для третьего пришлось бы еще раз повторить код и т. д. При высылке сигналов нам этого делать не нужно, так как объект-получатель задается методом QObject::connect(). Поэтому решение с использованием сигналов, в данном случае, будет смотреться более элегантно. Мы всего лишь один раз высылаем сигнал, вне зависимости от числа его получателей.


class MyProgressBar : public QProgressBar {
public:
    MyProgressBar(QWidget* pwgt = 0) : QProgressBar(pwgt)
    {
    }

    void customEvent(QEvent* pe)
    {
        if ((int)pe->type() == ProgressEvent::ProgressType) {
            setValue(((ProgressEvent*)(pe))->value());
        }
        QWidget::customEvent(pe);
    }
};

Для того чтобы виджет был в состоянии получать и правильно интерпретировать высылаемые потоком события, у нас есть две возможности. Первая возможность заключается в реализации класса фильтра событий и установки его в виджете. Второй способ заключается в наследовании класса виджета и перезаписи в нем метода customEvent().

В методе customEvent() мы проверяем тип полученного события, и если он соответствует типу нашего события ProgressType, то мы приводим его к классу ProgressEvent для того, чтобы вызвать метод value(). Возвращаемое этим методом значение передается, для отображения виджетом индикации прогресса, методу QProgressBar::setValue().


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

    prb.show();

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

В основной программе мы создаем виджет индикации прогресса и объект потока, после чего производим запуск потока вызовом метода start().

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

Читать далее: Синхронизация