1. Home
  2. Docs
  3. QT
  4. Genel
  5. QThread ile GUI Donmasını Önleme (Worker Pattern)

QThread ile GUI Donmasını Önleme (Worker Pattern)

Print

Masaüstü uygulamalarında kullanıcı deneyimini bozan en büyük problemlerden biri arayüzün kilitlenmesi veya donmasıdır. Bir dosya indirirken, büyük bir veri tabanını işlerken veya karmaşık bir algoritma çalıştırırken pencerenin yanıt vermemesi (Application Not Responding), işlemin ana iş parçacığında (Main/GUI Thread) çalışmasından kaynaklanır. Bu sorunu çözmek için en doğru yaklaşım Qt QThread kullanımı ve Worker Object Pattern mimarisidir.

Bu rehberde, QThread sınıfının çalışma mantığını, geleneksel yöntemlerle yapılan hataları ve Qt tarafından önerilen modern moveToThread desenini eksiksiz bir C++ örneğiyle inceliyoruz.


GUI Thread Neden Donar?

Tüm Qt uygulamaları temel bir olay döngüsü (Event Loop) üzerinde çalışır (QApplication::exec()). Bu döngü; klavye vuruşlarını, fare tıklamalarını, pencere yeniden çizimlerini (paint events) sırayla işler.

Eğer ana thread üzerinde 5 saniye süren yoğun bir döngü çalıştırırsanız:

  • Event Loop yeni olayları dinleyemez ve kuyruk tıkanır.
  • İşletim sistemi uygulamanın kilitlendiğini düşünür.
  • Kullanıcı butonlara tıklayamaz ve pencere donar.

Çözüm: Ağır hesaplamaları arka plan iş parçacığına (Worker Thread) devretmek ve arayüzü yalnızca sonuçlardan haberdar etmektir.


Yaygın Hata: QThread’i Subclass Yapmak vs Worker Pattern

Geçmişte birçok geliştirici QThread sınıfından miras alıp run() fonksiyonunu eziyordu (subclassing). Ancak bu yaklaşım ciddi bir yanılgı doğurur:

Dikkat Edilmesi Gereken Kural: QThread nesnesinin kendisi, yönettiği yeni iş parçacığında değil; kendisini oluşturan thread’de yaşar. Bu yüzden QThread sınıfı içine yazılan slotlar yanlış thread bağlamında çalışabilir.

Qt geliştirici ekibinin tavsiye ettiği en temiz ve modern yaklaşım Worker Object Pattern (İşçi Nesne Modeli) ve moveToThread yöntemidir.


Adım Adım Worker Pattern Mimarisi

İşçi nesne modelinde mantık üç aşamadan oluşur:

  1. İşi yapacak olan QObject türevi bağımsız bir sınıf (Worker) oluşturulur.
  2. Sıradan bir QThread nesnesi ayağa kaldırılır.
  3. worker->moveToThread(&thread) çağrısıyla işçi nesne arka plana taşınır.

1. Worker Sınıfının Tanımı

Aşağıdaki işçi sınıfı, arka planda ağır bir döngü işletirken her adımda arayüze ilerleme yüzdesi bildiren bir sinyal yayar:

// worker.h
#pragma once
#include <QObject>
#include <QThread>

class Worker : public QObject {
    Q_OBJECT

public:
    explicit Worker(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    void isiBaslat() {
        // Arka planda calisacak agir is
        for (int i = 1; i <= 100; ++i) {
            QThread::msleep(50); // Agir islemi simule ediyoruz
            emit ilerlemeGuncellendi(i);
        }
        emit isTamamlandi();
    }

signals:
    void ilerlemeGuncellendi(int yuzde);
    void isTamamlandi();
};

2. Main / Controller Tarafında Bağlantıların Kurulması

Şimdi bu işçiyi arka plan thread’ine taşıyalım ve sinyalleri bağlayalım:

// mainwindow.cpp
#include "mainwindow.h"
#include "worker.h"
#include <QThread>
#include <QDebug>

void MainWindow::agirislemibaslat() {
    QThread *thread = new QThread(this);
    Worker *worker = new Worker(); // Parent verilmez!

    // Isciyi arka plan thread'ine tasiyoruz
    worker->moveToThread(thread);

    // 1. Thread basladiginda iscinin slotunu tetikle
    connect(thread, &QThread::started, worker, &Worker::isiBaslat);

    // 2. Ilerleme sinyalini arayuzdeki ProgressBar'a bagla
    connect(worker, &Worker::ilerlemeGuncellendi, this, [this](int yuzde) {
        ui->progressBar->setValue(yuzde);
    });

    // 3. Is bittiginde temizlik yap
    connect(worker, &Worker::isTamamlandi, thread, &QThread::quit);
    connect(worker, &Worker::isTamamlandi, worker, &Worker::deleteLater);
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);

    // Thread'i baslat
    thread->start();
}

Çoklu İş Parçacığında Altın Kural: GUI’ye Doğrudan Erişmeyin!

Çoklu iş parçacığı programlamada en sık yapılan ve uygulamanın çökmesine (segmentation fault) yol açan hata, arka plan thread’inden doğrudan GUI nesnelerine (QLabel, QPushButton vb.) müdahale etmektir:

// KESINLIKLE YANLIS (Crash Sebebi):
void Worker::isiBaslat() {
    ui->label->setText("Bitti"); // FATAL: GUI nesneleri sadece GUI thread'inden degistirilebilir!
}

Doğru Yol: Arka plan nesnesi bir signal yaymalı, bu sinyal sitemizdeki Signals ve Slots Mekanizması rehberinde anlattığımız Qt::QueuedConnection sayesinde GUI thread’ine güvenle iletilmelidir.


Thread Yaşam Döngüsü ve Bellek Yönetimi

Yukarıdaki kodda dikkat edilmesi gereken en kritik ayrıntı deleteLater kullanımıdır:

  • worker->deleteLater(); iş tamamlandığında işçi nesnesinin belleğini sızıntı (memory leak) oluşturmadan güvenle temizler.
  • thread->deleteLater(); thread durup sonlandığında işletim sistemi kaynaklarını serbest bırakır.

Hata ayıklama süreçlerinde logları temiz bir şekilde izlemek için Manjaro QDebug Hatasının Çözümü yazımızdaki ipuçlarını da kullanabilirsiniz.

Daha gelişmiş thread havuzu (QThreadPool) ve eşzamanlılık kütüphaneleri için resmi Qt Threading Basics Dokümantasyonu sayfasından faydalanabilirsiniz.

Tags , , , , , ,

How can we help?