简体   繁体   中英

How to stop the execution of a thread

In the following code I have the class The Thread, which inherits from threading to execute a task in a different thread, in this case the task is to show a.gif image in a QLabel, while another task is executed.

In this case, when emitting the signal that executes the Finish() function, the tabWidget() widget changes from index to 1

But when we return to index 0 we can see that the .gif is still running in the QLabel.

So I want to know how to stop the execution of that thread, I tried

TheThread.kill()
TheThread.stop()

But it didn't work, this is the complete code with the .ui file and the image.gif

from PyQt5.QtWidgets import QMainWindow,QApplication,QTabWidget,QPushButton,QLabel
from PyQt5 import QtCore,QtGui
from PyQt5 import uic
import threading
import time

class TheThread(threading.Thread):
    def __init__(self,obj,fun):
        threading.Thread.__init__(self)

        self.obj = obj
        self.fun = fun

    def run(self):
        self.fun()
class Main(QMainWindow):
    signal = QtCore.pyqtSignal(object)
    def __init__(self):
        QMainWindow.__init__(self)
        uic.loadUi("Test.ui",self)

        self.Iniciar.clicked.connect(lambda:self.ShowImage())

        self.signal.connect(self.Finish)
        self._thread = TheThread(self,self.Fun1)

    def ShowImage(self):
        _movie = QtGui.QMovie("Loader.gif")
        self.Animacion.setMovie(_movie)
        self.Animacion.setScaledContents(True)
        _movie.start()

        self._thread.start()

    def Fun1(self):
        time.sleep(3)
        self.signal.emit(0)

    def Finish(self,signal):
        if signal == 0:
            time.sleep(1)

            self.tabWidget.setCurrentIndex(1)
            # TheThread.kill()
            TheThread.stop()


app = QApplication([])
m = Main()
m.show()
app.exec_()

File.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QTabWidget" name="tabWidget">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>0</y>
      <width>691</width>
      <height>441</height>
     </rect>
    </property>
    <widget class="QWidget" name="tab">
     <attribute name="title">
      <string>Tab 1</string>
     </attribute>
     <widget class="QLabel" name="Animacion">
      <property name="geometry">
       <rect>
        <x>220</x>
        <y>40</y>
        <width>231</width>
        <height>171</height>
       </rect>
      </property>
      <property name="text">
       <string>TextLabel</string>
      </property>
     </widget>
     <widget class="QLabel" name="Estatus">
      <property name="geometry">
       <rect>
        <x>220</x>
        <y>270</y>
        <width>271</width>
        <height>16</height>
       </rect>
      </property>
      <property name="text">
       <string>TextLabel</string>
      </property>
     </widget>
    </widget>
    <widget class="QWidget" name="tab_2">
     <attribute name="title">
      <string>Tab 2</string>
     </attribute>
    </widget>
   </widget>
   <widget class="QPushButton" name="Iniciar">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>460</y>
      <width>75</width>
      <height>23</height>
     </rect>
    </property>
    <property name="text">
     <string>PushButton</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

Image.gif 在此处输入图片说明

Your description does not match your code, nor with the correct logic.

  • The gif that is part of the GUI must be executed in the main thread (GUI thread) and the heavy task in the secondary thread.

  • Do not use time.sleep () in the main thread.

  • That it finishes executing the heavy task does not imply that it is finished executing the gif, you have to stop it.

Considering the above, a simpler approach is to create a QObject that lives in another thread and execute the task there, that allows you to add the started and finished signals.

import os
import time
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets, uic


current_dir = os.path.dirname(os.path.realpath(__file__))


def callback():
    # emulate task
    time.sleep(3.0)


class Worker(QtCore.QObject):
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()

    @QtCore.pyqtSlot(object)
    def task(self, fun):
        self.started.emit()
        fun()
        self.finished.emit()


class Main(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(Main, self).__init__(parent)

        filename = os.path.join(current_dir, "Test.ui")
        uic.loadUi(filename, self)

        self.Animacion.setScaledContents(True)

        thread = QtCore.QThread(self)
        thread.start()

        self._worker = Worker()
        self._worker.moveToThread(thread)
        self._worker.started.connect(self.show_image)
        self._worker.finished.connect(self.on_finished)

        wrapper = partial(self._worker.task, callback)
        self.Iniciar.clicked.connect(wrapper)

    @QtCore.pyqtSlot()
    def show_image(self):
        _movie = QtGui.QMovie("Loader.gif")
        self.Animacion.setMovie(_movie)

        self.Animacion.movie().start()

    @QtCore.pyqtSlot()
    def on_finished(self):
        self.tabWidget.setCurrentIndex(1)
        self.Animacion.movie().stop()


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Main()
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

@eyllanesc was right that you used the wrong thread and the better approach is to use a Qt GUI thread. However, I find it nice to demonstrate the multitude of ways of killing a thread in Python besides using the stop() or kill() methods (which btw, shouldn't they have been set to the self._thread variable you defined instead of the TheThread class?). These techniques are hackish in nature, but they work.

First, you can throw an exception, since exceptions always stop code from executing. This example uses the ctypes library:

import ctypes

def get_id(self): 

    # returns id of the respective thread 
    if hasattr(self, '_thread_id'): 
        return self._thread_id 
    for id, thread in threading._active.items(): 
        if thread is self: 
            return id

def raise_exception(self): 
    thread_id = self.get_id() 
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 
          ctypes.py_object(SystemExit)) 
    if res > 1: 
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) 
        print('Exception raise failure') 

self._thread.raise_exception();

You can also use a Boolean flag variable, that when set to true, executes a break statement, which ends the program. You could define the thread in a run() function and then the join() method is what essentially stops the thread:

def run(): 
   while True:
       global stop_threads 
       if stop_threads: 
          break

self._thread = threading.Thread(target = run)  
self_thread.start() 
stop_threads = True
self_thread.join()

You could also use a multiprocessing module, and instead of using a thread, you can use a process, which is very similar, the only difference being that threads run in the same memory space, while processes have separate memory. The terminate() method kills the process:

import multiprocessing 
import time 

def func(number): 
   for i in range(1, 10): 
       time.sleep(0.01) 
       print('Processing ' + str(number) + ': prints ' + str(number*i)) 

all_processes = [] 

for i in range(0, 3): 
   process = multiprocessing.Process(target=func, args=(i,)) 
   process.start() 
   all_processes.append(process) 

# Kill all processes after 0.03s  
time.sleep(0.03) 
for process in all_processes: 
   process.terminate()

Finally, you can kill a thread by setting it to a daemon. By default, threads don't end when the program ends, so daemon threads are those threads which are killed when the main program exits:

def func(): 
   while True: 
      time.sleep(0.5) 
      print('Thread alive, but it will die on program termination') 

self._thread = threading.Thread(target=func) 
self._thread.daemon = True
self._thread.start() 
time.sleep(2)
sys.exit()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM