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:
QThreadnesnesinin kendisi, yönettiği yeni iş parçacığında değil; kendisini oluşturan thread’de yaşar. Bu yüzdenQThreadsı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:
- İşi yapacak olan
QObjecttürevi bağımsız bir sınıf (Worker) oluşturulur. - Sıradan bir
QThreadnesnesi ayağa kaldırılır. 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.