Pazar, 30 Mart 2014 10:37

QThread doğru kullanımı

Yazan 
Öğeyi Oyla
(0 oy)

QThread::run() izleğimizin(thread) başlangıç noktasıdır.
QThread bir izleğin çalıştırmak için gerekli olan sınıftır. Bir izlek QThread::start() metodu ile başlatılır. QThread::start() ise izleğin gerçek çalışma bloğu olan QThread::run() metodunu çalıştırır. Yani programımızı yazarken kullandığımız main() fonksiyonu ne ise QThread::run() metoduda odur.  Eğer QThread den bir alt sınıf türetmemişsek ön tanımlı QThread::run() metodu QThread::exec() metodu ile bir olay işletim mekanizmasını başlatır.

QThread'in iki kullanım şekli vardır. QThread'den bir alt sınıf türetip bu alt sınıfdaki QThread::run() metodunu yeniden gerçekleştirildiği kullanım şekli ve tamamen bağımsız İşçi izlek kullanım şekli.

QThread'in birinci kullanım şekli
QThread::run() metodu izleğimizin başlangıç noktası olduğuna göre bu metod içerisinde çalışmayan hiçbir kod aslında bu izlekte çalışmıyor demektir.

Aşağıda QThread sınıfından thread sınıfı türetildi. Bu sınfıdaki QThread::run() metodu ve QThread::stop() yuvası (slot)  yeniden gerçekleştirildi.

// Qt 5 de derlendi
#include <QtWidgets>


class Thread : public QThread
{
    Q_OBJECT

public:
    Thread():m_stop(false)
    {}

public slots:
    void stop()
    {
        qDebug()<<"Thread::stop çağrımı ana izlekden yapıldı. Id : "<<currentThreadId();
        QMutexLocker locker(&m_mutex);
        m_stop=true;
    }

private:
    QMutex m_mutex;
    bool m_stop;

    void run()
    {
        qDebug()<<"İşçi izlek : "<<currentThreadId();
        while (1) {
            {
            QMutexLocker locker(&m_mutex);
            if (m_stop) break;
            }
            msleep(10);
        }
    }
};

#include "main.moc"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug()<<"Ana izlek Id : "<<QThread::currentThreadId();
    QPushButton btn("Stop Thread");
    Thread t;

    QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));
    QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));

    t.start();
    btn.show();
    return a.exec();
}



btn adlı QPushButton nesnemizin QPushButton::clicked() sinyalini izleğimizin QThread::stop() yuvasına bağladık. İzleğimizin QThread::finished() sinyalini de uygulamamızın QApplication::quit() yuvasına bağladık. Programı çalıştırdığımızda konsoldan aşağıdaki çıktıyı alırız. Fakat bu örnekteki önemli eksiklik QThread::run() metodunun QThread::exec() metodu ile başlatılan bir mesaj kuyruğunu yönetim döngüsünün veya olay yönetim mekanizmasının olmamasıdır.

Ana izlek Id :  0x7f6ee5732780 
İşçi izlek :  0x7f6ed9d67700 
Thread::stop çağrımı ana izlekden yapıldı. Id :  0x7f6ee5732780 

QThread Hatalı Kullanımı

Yukarıdaki örnek anlamak oldukça kolay fakat ya olay yönetim sistemini işçi izlekte kullanmak istersek ne yapmamız gerekir. Örnek olarak mesala belirli bir işlemi belirli periyotlarla yapmak istersek nasıl bir yol izlemeliyiz. Kabaca ilk akla gelen aşağıdaki adımları izleyelim.

  • Bir QThread::run metodunda bir QTimer oluşturuz.
  • QTimer::timeout siyalini izleğimizdeki bir yuvaya bağlarız.
#include <QtCore>

class Thread : public QThread
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Thread::onTimeout yuvasının çağrıldğı izlek id : "<<QThread::currentThreadId();
    }

private:
    void run()
    {
        qDebug()<<"İşçi izlek id : "<<currentThreadId();
        QTimer timer;
        connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<<"Ana izlek id : "<<QThread::currentThreadId();

    Thread t;
    t.start();

    return a.exec();
}



İlk bakışda bu kod doğru gözükebilir fakat çok yapılan bir hatayı içinde barındırmaktadır. Thread::run() metodu içerinse QTimer nesnemizi oluşturup bu nesnenin QTimer::timeout sinyalini aktif izleğimizin thread::onTimeout yuvasına bağladık. Zamanlayıcımızı ise her bir saniyede sonlanması için gerekli kodlamayı yazdık. Son olarak ise izleğimizin mesaj kuyruğunu işleyebilmesi için gerekli olan exec() kodunu çalıştırdık. Şimdi her bir saniyede onTimeOut yuvası çalışıyor. Fakat çıktığıya bakarsak önemli bir durumu anlayabiliriz.

Ana izlek id :  0x7fe8a587e780 
İşçi izlek id :  0x7fe89d9ff700 
Thread::onTimeout yuvasının çağrıldğı izlek id :  0x7fe8a587e780 
Thread::onTimeout yuvasının çağrıldğı izlek id :  0x7fe8a587e780 
Thread::onTimeout yuvasının çağrıldğı izlek id :  0x7fe8a587e780 
Thread::onTimeout yuvasının çağrıldğı izlek id :  0x7fe8a587e780 
Thread::onTimeout yuvasının çağrıldğı izlek id :  0x7fe8a587e780 

Şimdi burada dikkat edecek olursak onTimeout yuvası ana izleğimizin üzerinde çalışmakta. Bunun sebebi ise dikkat ederseniz Thread t tanımlamamız main fonksiyonu içerinde gerçekleştirilmekte. Bu durumda t izleği aslında ana izleğe ait bir nesnedir. Bu durumda onTimeout yuvası ana izlek üzerinden çağrılır. Programımız düzgün çalışsa dahi yanlış bir kullanım metodudur ve ileride ciddi hatalara sebebiyet verebilir.
QThread doğru kullanım şekli 1

Son örnekteki hatamızıda gördükten sonra şu neticeyi çıkartabiliriz. Eğer bizim izleğimizin QThread::run dışındaki tüm metodları izlecimizin dışındaki bi izlecse olay mekanizmasını kullanabilmek için bir işçi nesnesini QThread::run içerisinde çalıştırıp bu işçi nesnenin yuvalarına sinyalleri bağlarız. Böylelikle bizim işçi nesnemiz QThread::run içerisinde tanımlandığı için bu nesnenin tüm metodlarıda aynı izleğin içinde çalıştırılır. Aşağıdaki kod buna göre düzeltilmiş halidir.
 

#include <QtCore>

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout yuvasının çalıştığı izlek : "<<QThread::currentThreadId();
    }
};

class Thread : public QThread
{
    Q_OBJECT

private:
    void run()
    {
        qDebug()<<"İşçi izlek id : "<<currentThreadId();
        QTimer timer;
        Worker worker;
        connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<<"Ana izlek id : "<<QThread::currentThreadId();

    Thread t;
    t.start();

    return a.exec();
}


Aşağıda bu uygulamanın sonucunu görüyoruz

Ana izlek id :  0x7f690145c780 
İşçi izlek id :  0x7f68f95df700 
Worker::onTimeout yuvasının çalıştığı izlek :  0x7f68f95df700 
Worker::onTimeout yuvasının çalıştığı izlek :  0x7f68f95df700 

Problemin çözüldüğünü Worker::onTimeout yuvasının çalıştğı izleğin işçi izlek id ile aynı olmasından anlıyoruz.

Uygulama başarı ile çalışmasına rağmen dikkatimizi çeken bir durum var. QThread::run() yeniden gerçekleştirilen metodda aslında QThread::exec() metodunu çalıştırmasından başka bir özellği yok. Yaklaşımımızı böyle bir durum için şu şekilde değiştirebiliriz. İşçi nesnemize gerekli yuvaları tanımlayıp bağlantıları yaptıkdan sonra istediğimiz bir QThread sınıfından oluşturulmuş bir izleğe işçi nesnemizi taşıyabiliriz. Böylelikle boş yere QThread den yeni bi sınıf türetmemize gerek kalmaz.

QThread'ın ikinci kullanım şekli

 

#include <QtCore>

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout yuvasının çalıştığı izlek id : "<<QThread::currentThreadId();
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<<"Ana izlek id : "<<QThread::currentThreadId();

    QThread t;
    QTimer timer;
    Worker worker;

    QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
    timer.start(1000);

    timer.moveToThread(&t);
    worker.moveToThread(&t);

    t.start();

    return a.exec();
}


Program çıktısı aşağıdaki gibi olur.

Ana izlek id :  0x7fdb355c6780 
Worker::onTimeout yuvasının çalıştığı izlek id :  0x7fdb2d747700 
Worker::onTimeout yuvasının çalıştığı izlek id :  0x7fdb2d747700 
Worker::onTimeout yuvasının çalıştığı izlek id :  0x7fdb2d747700 

Sonuç tam istediğimiz gibi olur. Aslında QTimer'ı moveToThread metodu ile alt izleğe taşımamıza gerek yok. Aşağıdaki şekilde de main fonksiyonumuzu çalıştırabiliriz.

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug()<<"Ana izlek id : "<<QThread::currentThreadId();

    QThread t;
    QTimer timer;
    Worker worker;

    QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
    timer.start(1000);

//    timer.moveToThread(&t);
    worker.moveToThread(&t);

    t.start();

    return a.exec();
}



İki durumu karşılaştıracak olursak
1.Örnek 2.Örnek
timeout() sinyali alt izlekden yayılır. timeout() sinyali ana izlekden yayılır.
Zamanlayıcı(timer) ve işçi(worker) aynı izlekden çalışdır. Birbirlerine bağlantı şekli direk bağlantıdır. Zamanlayıcı(timer) ve işçi(worker) farklı izleklerde çalışır. Birbirlerine bağlantı şekli kuyruklu bağlantıdır (queued connection).
Sinyalin yayınlandığı ve yuvanın çağrıldığı izlekler aynıdır. Yuva alt izlek olan yaşayan izlekden çağrılmıştır.

Kuyruklu bağlantı mekanizması sayesinde sinyal yuva bağlantısı farklı izlekler arasında yapılabilmektedir.

Kısaca
  • QThread sınıfından yeni bir sınıf türetip burada run metodunu yeniden tanımlamak oldukça anlaşılır ve pratiktir. Fakat eğer izleğimizin bir olay mekanizmasına sahip olmasını istiyorsak iyi bir yöntem değildir.
  • İşçi nesneler oluşturup bunları istediğimiz izleklere taşımamız izleklerdeki olay döngü mekanizmasını rahatlıkla kullanabilmemizi sağladığı gibi yeni bir QThread alt sınıfı tanımlamamıza gerek kalmaz.
İngilizce çeviri bu adresten yapılmıştır.
Okunma 8997 defa Son Düzenlenme Pazar, 30 Mart 2014 14:19
Ufuk Yıldırım

Yazılım Geliştirme Uzmanı

Web site: www.ufuk.biz

Yorum Ekle

Gerekli olan (*) işaretli alanlara gerekli bilgileri girdiğinizden emin olun. HTML kod izni yoktur.