Thread içindeki döngüyü başa sarmanın veya döngüyü kırıp thread'i öldürmenin yöntemi nedir?

Merhaba, PyQt5 ile tamsayılarla çarpma ve bölme üzerine bir tür arayüz oyunu tasarlamaya çalışıyorum

Süre sayacı için QLCDNumber bağlantısını bir yerden bulduğum kod ile yaptım. oluşturduğum thread içindeki döngü ile 10’dan geriye sorunsuz sayıp döngü bitince istediğim işlemi yapıp thread kayboluyor. Ama ben her soru seçiminden sonra süre sayacını tekrar 10’dan başlatmak istiyorum. Ama görüyorum ki ya döngüyü başa alabilmem ya da döngüyü kırıp var olan thread’i öldürmem gerek ki arkadan gelen yeni thread sorunsuz şekilde sayacı tekrar 10 'dan başlatabilsin ama halihazırda önceki thread’in varlığı sanırsam buna engel oluyor. döngüyü kırıp çıksam da bu kez yine arkdan gelen yeni thread sayacı devralamıyor.

Kodlar şu şekilde

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtCore, Qt
import time
import threading
from random import randint, choice, shuffle
from PyQt5.QtCore import *
from math import sqrt
from Tam_Sayilarda_Carpma_Bolme_Oyunu_python import Ui_MainWindow


class TamSayiCarpmaBolmeOyunu(QMainWindow):
    counter_x = pyqtSignal(int)
    counting = False

    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.timer = QTimer()
        self.ui.setupUi(self)
        self.counter_x.connect(self.update_lcd)
        self.skor = 0  # Skor başlangıç değeri
        self.sayac = 0  # sayaç başlangıç değeri
        self.numberOfWrongAnswers = 0  # yanlış cevap sayacı başlangıç değeri
        self.generateRandomValues()  # Ekrana işlem oluşturup basar.
        self.UIComponentSettings()
        self.show()

    def mainActionBoard(self, optionChoise):
        """
        A,B, C butonlarından biri basılıp seçim yapılınca, eğer yapılan seçim işlem sonucu ile aynı ise veya farklı  ise
        bir takım işlemler yerine getirir.
        :param optionChoise:
        :return:
        """

        self.continueSignal = False

        if self.result == optionChoise: # sonuç seçimle aynı ise...
            self.skor += 10 # skor 10 arttır
            self.ui.label_tepki.setStyleSheet("image: url(:/DogruYanlis/images/DogruYanlis/dogru.png);") # doğru cevap tepki resmi
            self.timer.singleShot(2000, self.backEmojiFace) # 2 saniye sonra tekrar emojiye geri dön.
            self.counter() # her tur için sayac işlemleri
            print(self.sayac, ".soruyu doğru bildiniz. skor:", self.skor)


        else:
            self.wrongAnswerCounter() # yanlış cevap sayacına bağlan ve bir canı azalt
            self.ui.label_tepki.setStyleSheet("image: url(:/DogruYanlis/images/DogruYanlis/yanlis.png);")
            self.timer.singleShot(2000, self.backEmojiFace)
            self.counter()
            print(self.sayac, ".soruyu yanlış yaptınız")




    def UIComponentSettings(self):
        """
        butonları seçeneklerdeki değer ile birlikte işlem fonksiyonuna bağlar
        :return:
        """
        self.ui.pushButton_A_secenek.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_A.text())))
        self.ui.pushButton_B_secenek_.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_B.text())))
        self.ui.pushButton_C_secenek.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_C.text())))

    def generateRandomValues(self):
        """
        Üretilen rastgele sayılarla tamsayılarla çarpma
        veya bölme işlemi oluşturup bunu ekrandaki label'lara basar.Şıkları
        beirleyen ayrı bir metoda (self.displayOptions) bağlanır.
        :return:None
        """
        self.num1 = randint(-10, 11)
        self.num2 = randint(-10, 11)
        self.kat = randint(1, 7)

        self.multiplyDivision = choice(["x", ":"])

        if self.multiplyDivision == "x":
            self.ui.label_birinciDeger.setText(f"({self.num1})" if self.num1 < 0 else f"{self.num1}")
            self.ui.label_CarpmaBolme.setText("x")
            self.ui.label_ikinciDeger.setText(f"({self.num2})" if self.num2 < 0 else f"{self.num2}")
            self.result = self.num1 * self.num2
            self.displayOptions()


        else:
            while (self.num2 == 0):
                self.num2 = randint(-10, 11)
            self.num1 = self.num2 * self.kat
            self.ui.label_birinciDeger.setText(f"({self.num1})" if self.num1 < 0 else f"{self.num1}")
            self.ui.label_CarpmaBolme.setText(":")
            self.ui.label_ikinciDeger.setText(f"({self.num2})" if self.num2 < 0 else f"{self.num2}")
            self.result = int(self.num1 / self.num2)
            self.displayOptions()

        self.startCounting() # Tüm işlemlerden sonra sayacı başlatmak için


    def update_lcd(self, value):  #QLCDNumber için güncelleme
        if value == 1:
            self.ui.lcdNumber_sureSayaci.display(value)
            self.timeEndingMessage() #
        else:
           self.ui.lcdNumber_sureSayaci.display(value)

    def startCounting(self): # QLCDNumber sayacı için thread oluştur ve self.countDown'a bağlan
        if  not self.counting:
            self.counting = True
            self.thread_count = threading.Thread(target=self.countDown, daemon=True)
            self.thread_count.start()


    def countDown(self): # QLCDNumber sayacını 10'dan geriye doğru sayılması
        for i in range(10,0,-1):
            self.counter_x.emit(int(i))
            time.sleep(1)
        self.counting = False

    def displayOptions(self):

        if self.multiplyDivision == "x" and (self.num1 == 0 or self.num2 == 0):
            self.wrongOption_1 = self.result + 1
            self.wrongOption_2 = self.result + 2
        else:
            self.wrongOption_1 = self.result * 2
            self.wrongOption_2 = self.result * 3

        self.options = [self.wrongOption_1, self.wrongOption_2, self.result]
        shuffle(self.options)

        self.ui.label_secenek_A.setText(str(self.options[0]))
        self.ui.label_secenek_B.setText(str(self.options[1]))
        self.ui.label_secenek_C.setText(str(self.options[2]))

    def counter(self):
        """
        Kullanıcı cevabından sonra sayacın arttırılması
        ve sayac ile yanlış cevap sayacının durumlarına başlı olarak
        oyun turunun nasıl sona erdirileceğinden sorumlu.
        :return: None
        """

        self.sayac += 1

        if self.sayac < 5 and self.numberOfWrongAnswers < 3:
            self.generateRandomValues()
        elif self.sayac < 5 and self.numberOfWrongAnswers == 3:
            self.wrongCounterEndingMessage()
        elif self.sayac == 5 and self.numberOfWrongAnswers < 3:
            self.counterEndingMessage()

    def counterEndingMessage(self):
        """
        5 soru sonunda oyunun bitirilmesi durumunda ekranda skoru gösteren
        bitirme mesajı
        Kullanıcı devam etmek isterse oyunu sıfırlar, aksi takdirde pencereyi kapatır.
        :return: None
        """
        m_box = QMessageBox.question(self, "SKOR SONUCU",
                                     "Oyun bitti! Skorunuz:" f"{self.skor}/50"
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No )

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def wrongCounterEndingMessage(self):
        """
        Kullanıcı geçerli tur içinde 3 yanlış cevap verirse oyunu
        tükenen canlar üzerinden bitirir. Skor gösterilmez, kullanıcı başarısız sayılır.
        Kullanıcı tekrar oynamak isterse oyunu resetler tekrar başlatır, istemezse kapatır.
        :return: none
        """
        m_box = QMessageBox.question(self, "OYUNU BİTTİ",
                                     "HİÇ CANINIZ KALMADI :("
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No)

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def timeEndingMessage(self):
        m_box = QMessageBox.question(self, "SÜRE BİTTİ!",
                                     "Malesef Süreniz Bitti!"
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No)

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def wrongAnswerCounter(self):
        """
        Yanlış cevap verildiğinde yanlış cevap sayacını 1 arttrır ve
        3 candan birini siler.
        :return: None
        """
        self.numberOfWrongAnswers += 1
        if self.numberOfWrongAnswers == 1:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/2_kalp.png);")
        elif self.numberOfWrongAnswers == 2:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/1_kalp.png);")
        elif self.numberOfWrongAnswers == 3:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/0_kalp.png);")

    def backEmojiFace(self):
        # Tekrar öylece durma tepkisi veren emojiyi çağırır.
        self.ui.label_tepki.setStyleSheet("image: url(:/DogruYanlis/images/DogruYanlis/selamYuz.png);")

    def resetUi(self):
        """
        Oyun turu bittiğinde kullanıcı tekrar oynamak isterse oyun
        parametrelerini başlangıç durumuna getirir. Yani oyunu resetler/sıfırlar
        ve yeni baştan bir tur başlatır.
        :return: None
        """
        self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/3_kalp.png);")
        self.skor = 0
        self.numberOfWrongAnswers = 0
        self.sayac = 0
        self.generateRandomValues()


app = QApplication(sys.argv)
window = TamSayiCarpmaBolmeOyunu()
sys.exit(app.exec())

qt designer ile tasarlandı. import dosyalar gerekli olursa paylaşabilirim.
İlginiz ve yardımlarınız için şimdiden teşekürlr :smiley:

Thread’i “basa sarmanin” veya oldurmenin dogru yontemi bunu thread icinde yapmak. Thread icine “x olursa basa don” veya “y olursa ol” gibi bir kod eklemen lazim. Bu x/y kosulu iki thread tarafindan da gorulebilen basit bir degiskenin degeri olabilir. Daha komplike durumlarda threading modulundeki senkronizasyon primitifleri var. (Condition, Event, etc.)

Hepsine alternatif olarak, Qt’de kendi thread’lerinde calisan timer siniflari var diye hatirliyorum. Onlardan biri bu coklu thread zorluguna bulasmadan kullanilabilir.

QLCDNumber zaten başlı başına bir sıkıntı oldu. Sayacı ona bağlayamadım. Stackoverflow’dan bulduğum kod ile ayarladım. Basitçe döngüyü başa alabilirim ya da döngüden çıkıp akabinde thread’i yok edebilirim dedim ama olmadı. For döngüsü kırılınca sayaç donuyor. Aynı threadi while ile sonsuza aldığımda sayaç 1 olunca tekrar 10 dan başlıyor ama bu kez da for kırılınca while yine for döngüsünü başa almıyor. Yine sayaç donuyor. Döngü her türlü sorun teşkil ediyor. Aynı thread İçinde countDown() fonks. Nasıl başa sarabirim. Belki qtimer ile bir şeyler olabilir di ama QLCDNumber biraz sıkıntı olunca için işine sinyaller flan girdi. Ben de sinyallere pek hakim değilim açıkçası.

Merhaba, qt de thread’ lerle işlem yapacaksanız pyqt5'de signal-slot işlemlerini öğrenmenizi tavsiye ederim, thread’de işe yarayacağını düşünüyorum. Yani bence sana bir signal-slot mekanizması lazım gibi duruyor.

İnternetten araştırarak öğrenebileceğin gibi uzun zaman önce şöyle bir yazı kaleme almıştım. Belki başlangıç niyetine okuyabilirsin Yazı burada

Ek olarak bahsettiğim bu signal-slot mekanizmasının threadler ile kullanımına da internetten bakabilirsin.

Edit: kodlarınızı incelerken şimdi fark ettim de, threading.Thread yerine Pyqt5 'deki QThread kullanın. Adamlar kendi gui’leri için kendilerine ait thread class 'ları yazmışlar. Şiddetle pyqt5 de threading e ait olan Thread i kullanmayın, onun yerine QThread kullanın. Sorunun bundan kaynaklı olabilir.

QLCDNumber nedir bir fikrim yok. Sadece mesajlarda ve comment’lerde geciyor, kodda yok.

Dogrusunun bu oldugunu soyledigime gore neden olmadigini acman lazim. Yoksa tek diyebilecegim: “Olur, olur.”

Buna inanmasi zor. Kodu gormemiz lazim.

Bu arada problemi QT’den bagimsiz cozdun mu? Bir thread input beklerken baska bir thread geri saysin mesela, input gelince sifirlansin veya basa donsun?

Öneriniz için teşekürler . Gerçekten QThread bu konuda işimi halletti. QThread için yeni bir sınıf yazdım terminate() metodu sayesinde onu rahatça öldürebildim.

aynı zamanda size de teşekür ederim. Bunun önemini kavramam dönüm noktası oldu denebilir. Her arkadan gelen thread önceki sonlanmamış thread ile karışıyordu. while döngüsü için tek bir Thread ile sonsuz döngü kurdum ve butona basıldığında artık içerdeki for kırılınca sayacım başa sarıyor.
Kodun nihai halini paylaşıyorum:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import QtCore, Qt
import time
from random import randint, choice, shuffle
from PyQt5.QtCore import *
from Tam_Sayilarda_Carpma_Bolme_Oyunu_python import Ui_MainWindow


class TamSayiCarpmaBolmeOyunu(QMainWindow):

    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.timer = QTimer()
        self.ui.setupUi(self)
        self.skor = 0  # Skor başlangıç değeri
        self.sayac = 0  # sayaç başlangıç değeri
        self.resetUiEnabled = False # başlangıçta programın reset halinde olmadığını belirtir.
        self.numberOfWrongAnswers = 0  # yanlış cevap sayacı başlangıç değeri
        self.startCounting() # süre sayacını başlatmak için QThread ilk başlatılıyor
        self.generateRandomValues()  # Ekrana işlem oluşturup basar.
        self.UIComponentSettings()  # Butonları fonksiyona bağlar
        self.show()

    def UIComponentSettings(self):
        """
        butonları seçeneklerdeki değer ile birlikte işlem fonksiyonuna bağlar
        :return:
        """
        self.ui.pushButton_A_secenek.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_A.text())))
        self.ui.pushButton_B_secenek_.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_B.text())))
        self.ui.pushButton_C_secenek.clicked.connect(lambda optionChoise: self.mainActionBoard(optionChoise=int(self.ui.label_secenek_C.text())))


    def mainActionBoard(self, optionChoise):
        """
        A,B, C butonlarından biri basılıp seçim yapılınca, eğer yapılan seçim işlem sonucu ile aynı ise veya farklı  ise
        bir takım işlemler yerine getirir.
        :param optionChoise:
        :return:
        """
        if self.result == optionChoise: # sonuç seçimle aynı ise...
            self.rightAnswerAction()
        else:
            self.wrongAnswerAction()

    def rightAnswerAction(self):
        self.skor += 10  # skor 10 arttır
        print(self.sayac + 1, ".soruyu doğru bildiniz. skor:", self.skor)
        self.ui.label_tepki.setStyleSheet(
            "image: url(:/DogruYanlis/images/DogruYanlis/dogru.png);")  # doğru cevap tepki resmi
        self.timer.singleShot(2000, self.backEmojiFace)  # 2 saniye sonra tekrar emojiye geri dön.
        self.counter()  # her tur için sayac işlemleri


    def wrongAnswerAction(self):
        print(self.sayac + 1, ".soruyu yanlış yaptınız")
        self.wrongAnswerCounter()  # yanlış cevap sayacına bağlan ve bir canı azalt
        self.ui.label_tepki.setStyleSheet("image: url(:/DogruYanlis/images/DogruYanlis/yanlis.png);")
        self.timer.singleShot(2000, self.backEmojiFace)
        self.counter()


    def generateRandomValues(self):
        """
        Üretilen rastgele sayılarla tamsayılarla çarpma
        veya bölme işlemi oluşturup bunu ekrandaki label'lara basar.Şıkları
        beirleyen ayrı bir metoda (self.displayOptions) bağlanır.
        :return:None
        """
        self.num1 = randint(-10, 11)
        self.num2 = randint(-10, 11)
        self.kat = randint(1, 7)

        self.multiplyDivision = choice(["x", ":"])

        if self.multiplyDivision == "x":
            self.ui.label_birinciDeger.setText(f"({self.num1})" if self.num1 < 0 else f"{self.num1}")
            self.ui.label_CarpmaBolme.setText("x")
            self.ui.label_ikinciDeger.setText(f"({self.num2})" if self.num2 < 0 else f"{self.num2}")
            self.result = self.num1 * self.num2
            self.displayOptions()


        else:
            while (self.num2 == 0):
                self.num2 = randint(-10, 11)
            self.num1 = self.num2 * self.kat
            self.ui.label_birinciDeger.setText(f"({self.num1})" if self.num1 < 0 else f"{self.num1}")
            self.ui.label_CarpmaBolme.setText(":")
            self.ui.label_ikinciDeger.setText(f"({self.num2})" if self.num2 < 0 else f"{self.num2}")
            self.result = int(self.num1 / self.num2)
            self.displayOptions()

        if self.resetUiEnabled == False: # oyun reset yapılmamışsa...
            self.count.continueSignal = False #  Sayacı baştan başlatma komutu verir.
        else:
             pass


    def update_lcd(self, value):  #QLCDNumber için güncelleme
            if value == 1:
                self.ui.lcdNumber_sureSayaci.display(value)
                self.wrongAnswerAction()

            else:
                self.ui.lcdNumber_sureSayaci.display(value)

    def startCounting(self):
        self.count = SayacThread()
        self.count.sinyal.connect(self.update_lcd)
        self.count.start()


    def displayOptions(self):

        if self.multiplyDivision == "x" and (self.num1 == 0 or self.num2 == 0):
            self.wrongOption_1 = self.result + 1
            self.wrongOption_2 = self.result + 2
        else:
            self.wrongOption_1 = self.result * 2
            self.wrongOption_2 = self.result * 3

        self.options = [self.wrongOption_1, self.wrongOption_2, self.result]
        shuffle(self.options)

        self.ui.label_secenek_A.setText(str(self.options[0]))
        self.ui.label_secenek_B.setText(str(self.options[1]))
        self.ui.label_secenek_C.setText(str(self.options[2]))

    def counter(self):
        """
        Kullanıcı cevabından sonra sayacın arttırılması
        ve sayac ile yanlış cevap sayacının durumlarına başlı olarak
        oyun turunun nasıl sona erdirileceğinden sorumlu.
        :return: None
        """

        self.sayac += 1

        if self.sayac < 5 and self.numberOfWrongAnswers < 3:
            self.generateRandomValues()
        elif self.sayac < 5 and self.numberOfWrongAnswers == 3:
            self.count.terminate()
            self.wrongCounterEndingMessage()
        elif self.sayac == 5 and self.numberOfWrongAnswers < 3:
            self.count.terminate()
            self.counterEndingMessage()
        elif self.sayac == 5 and self.numberOfWrongAnswers == 3:
            self.count.terminate()
            self.wrongCounterEndingMessage()

    def counterEndingMessage(self):
        """
        5 soru sonunda oyunun bitirilmesi durumunda ekranda skoru gösteren
        bitirme mesajı
        Kullanıcı devam etmek isterse oyunu sıfırlar, aksi takdirde pencereyi kapatır.
        :return: None
        """
        m_box = QMessageBox.question(self, "SKOR SONUCU",
                                     "Oyun bitti! Skorunuz:" f"{self.skor}/50"
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No )

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def wrongCounterEndingMessage(self):
        """
        Kullanıcı geçerli tur içinde 3 yanlış cevap verirse oyunu
        tükenen canlar üzerinden bitirir. Skor gösterilmez, kullanıcı başarısız sayılır.
        Kullanıcı tekrar oynamak isterse oyunu resetler tekrar başlatır, istemezse kapatır.
        :return: none
        """
        m_box = QMessageBox.question(self, "OYUNU BİTTİ",
                                     "HİÇ CANINIZ KALMADI :("
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No)

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def timeEndingMessage(self):
        m_box = QMessageBox.question(self, "SÜRE BİTTİ!",
                                     "Malesef Süreniz Bitti!"
                                     "\nTekrar Oynamak ister misiniz?", QMessageBox.Yes | QMessageBox.No)

        if m_box == QMessageBox.Yes:
            self.resetUi()
        else:
            window.close()

    def wrongAnswerCounter(self):
        """
        Yanlış cevap verildiğinde yanlış cevap sayacını 1 arttrır ve
        3 candan birini siler.
        :return: None
        """
        self.numberOfWrongAnswers += 1
        if self.numberOfWrongAnswers == 1:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/2_kalp.png);")
        elif self.numberOfWrongAnswers == 2:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/1_kalp.png);")
        elif self.numberOfWrongAnswers == 3:
            self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/0_kalp.png);")

    def backEmojiFace(self):
        # Tekrar öylece durma tepkisi veren emojiyi çağırır.
        self.ui.label_tepki.setStyleSheet("image: url(:/DogruYanlis/images/DogruYanlis/selamYuz.png);")

    def resetUi(self):
        """
        Oyun turu bittiğinde kullanıcı tekrar oynamak isterse oyun
        parametrelerini başlangıç durumuna getirir. Yani oyunu resetler/sıfırlar
        ve yeni baştan bir tur başlatır.
        :return: None
        """
        self.resetUiEnabled = True
        self.ui.label_canlar.setStyleSheet("image: url(:/Canlar/images/CanBari/3_kalp.png);")
        self.skor = 0
        self.numberOfWrongAnswers = 0
        self.sayac = 0
        self.startCounting()
        self.resetUiEnabled = False
        self.generateRandomValues()


class SayacThread(QThread):
    sinyal = pyqtSignal(object) # bir sinyal nesmesi oluşturuldu.
    continueSignal = True # sayaç döngüsüsünden çıkmak için yeri geldiğinde 'False' ayarlanır.

    def __init__(self):
        super().__init__()


    def run(self): # Oluşturulan QThread nesnesi bu metoda bağlanır ve çalıştığı sürece 10'dan geri sayma sinyali yayar.
        print("run fonksiyonu çalıştı")
        while self.isRunning():
            for i in range(10, 0, -1):
                if self.continueSignal == True: # şık seçim butona baslılırsa False olur ve döngüden çıkılır.
                    self.sinyal.emit(int(i)) # 'sinyal' adlı sinyal nesnesi integer türünde sinyali yayar.
                    time.sleep(1) # her saniye bir sinyal yaymak için
                else:
                    break
            self.continueSignal = True # Sayaç tekrar başlayınca döngünün devam etmesi için


app = QApplication(sys.argv)
window = TamSayiCarpmaBolmeOyunu()
sys.exit(app.exec())

1 Beğeni