【问题标题】:Python/PySide: How can i destroy a terminated thread object?Python/PySide:如何销毁终止的线程对象?
【发布时间】:2015-03-23 13:15:04
【问题描述】:

我想实现一个按钮来停止带有进程的线程,它可以工作但没有按预期工作:我无法删除线程对象。 (编辑:似乎删除了对线程对象的引用,但是通过删除线程对象并不会自动断开信号,我可以通过信号访问它。)

我有一个带有类 thread_worker 的模块和一个作为进程运行的复杂处理函数:

from PySide.QtCore import *
from PySide.QtGui import *
import multiprocessing as mp
import time

# this function runs as a process
def complex_processing(queue):
    # do something
    ...

class thread_worker(QThread):
    message_signal = Signal(str)
    stop_thread_signal = Signal()

    def __init__(self, prozessID, sleepTime, parent=None):
        super(ThreadProzessWorker, self).__init__(parent)
        self.queue = mp.Queue()
        self.process = mp.Process(target=complex_processing, args=(self.queue,))
        self.timeStamp = int(time.time())

    def run(self):
        self.process.start()
        self.process.join()

    @Slot()
    def stop_process_and_thread(self):
        if self.isRunning():
            self.message_signal.emit("Thread %d is running!" % self.timeStamp)
            if self.process.is_alive():
                self.process.terminate()
                self.process.join()      
            self.stop_thread_signal.emit()   
            #self.terminate() # does it works at this place?       
        else:
            self.message_signal.emit("Thread %d is not running!" % self.timeStamp)

我的应用程序中有两个按钮来创建/运行和终止线程对象。

...
...
# Buttons
self.button_start_thread = QPushButton("Start Thread")
self.button_start_thread.clicked.connect(self.start_thread)
self.button_stop_thread = QPushButton("Stop Thread")
...
...
@Slot()
def start_thread(self):
    self.new_thread = thread_worker(self)
    self.button_stop_thread.clicked.connect(self.new_thread.stop_process_and_thread)
    self.new_thread.stop_thread_signal.connect(self.stop_thread)
    self.new_thread.message_signal.connect(self.print_message)
....
....
@Slot()
def stop_thread(self):
    self.new_thread.terminate()
    #self.button_stop_thread.disconnect(self.new_thread)
    del(self.new_thread)

@Slot(str)
def print_message(self, message):
    print(message)
...
...

如果我启动和停止第一个线程 - 它工作正常并终止,但如果我再次单击“停止”按钮,输出是:

Thread 1422117088 is not running!

我不明白:self.new_thread 对象是否被del(self.new_thread) 删除?如果它被删除,我如何访问这个对象?如果我再次启动和停止一个新线程,输出是:

Thread 1422117088 is not running!  # not expected, the thread object is deleted!
Thread 1422117211 is running!      # expected

现在我再做一次(开始和停止),输出是:

Thread 1422117088 is not running!   # not expected, the thread object is deleted!
Thread 1422117211 is not running!   # not expected, the thread object is deleted!
Thread 1422117471 is running!       # expected

等等……

第一个问题: 我不明白为什么不删除旧线程?为什么我可以访问它们?我认为这不好:如果后台有太多线程(未删除的对象),我的应用程序有时会崩溃。

第二个问题: 我不明白如果我删除对象self.new_thread,为什么信号没有断开?我不想手动断开信号:如果我有很多信号,我可能会忘记断开一些信号。

第三个问题: 我选择这种方式来停止一个进程的线程。还有其他更好的方法吗?

更新:

线程对象似乎已被破坏:

del(self.new_thread)
print(self.new_thread)
Output: AttributeError: 'MainWindow' object has no attribute 'new_thread'

但是我的信号没有断开!? Here 被描述为:“当涉及的任何一个对象被破坏时,信号槽连接被删除。”它在我的代码中不起作用。

【问题讨论】:

  • 最后一枪...删除 print(self.new_thread),或将其包含在 try-except 子句中
  • 我现在的代码中没有print(self.new_thread),那只是为了检查对象是否被删除。我们看到,引用 self.new_thread 被删除,但对象没有被删除。

标签: python multithreading pyqt multiprocessing pyside


【解决方案1】:

你是对的,你的问题是,你的对象没有被删除。您只删除引用self.new_thread。问题出在这一行:

self.new_thread = thread_worker(self)

原因是线程的父级self 是存活的。只要父母还活着,对象self.new_thread 就不会被销毁。试试这样的:

self.threadParent = QObject()
self.new_thread = thread_worker(self.threadParent)

现在你也删除了父self.threadParent

self.new_thread.terminate()
del(self.new_thread)
del(self.threadParent)

您的信号现在应该断开了。

您不需要以下行,因为在您发出信号 stop_thread_signal 后对象 self.new_thread 已经被删除:

#self.terminate() # does it works at this place?

【讨论】:

    【解决方案2】:

    这可能是因为您的进程不包含任何内容并在它启动后完成。因此

    中的代码
    def stop_process_and_thread(self):
        if self.isRunning():
            ...
            if self.process.is_alive():
                self.process.terminate()
                self.process.join()      
                self.stop_thread_signal.emit()   
                #self.terminate() # does it works at this place?  
    

    在 if self.process.is_alive() 之后永远不会到达。因此,stop_thread_signal 永远不会发出,因此线程既不会终止也不会被删除

    将您的测试 complex_processing 函数更改为

    def complex_processing(queue):
    # do something
      while(True):
        print "in process"
        time.sleep(0.2)
    

    现在要实现你想要的效果,你应该将线程存储在一个列表中,这样 start_thread 应该变成

    def start_thread(self):
        n = len(self.new_thread)
        self.new_thread.append(thread_worker(self))
        self.new_thread[-1].setTerminationEnabled(True)
        self.new_thread[-1].start()
        if n>0:
            self.button_stop_thread.clicked.disconnect()
        self.button_stop_thread.clicked.connect(self.new_thread[-1].stop_process_and_thread)
        print self.new_thread
        self.new_thread[-1].stop_thread_signal.connect(self.stop_thread)
        self.new_thread[-1].message_signal.connect(self.print_message)
    

    在此请注意,在设置与列表的最后一个线程(您新添加的线程)的连接之前,您必须首先断开按钮 clicked() 信号与所有插槽的连接。这是因为如果您启动多个线程,clicked() 信号将连接到所有线程的插槽。因此 stop_process_and_threadstop_thread 将被调用的次数与正在运行的线程数一样多,并且具有不可预测的顺序。 通过断开信号,只有最后启动的线程被终止并从列表中删除。还要确保设置了 setTerminationEnabled

    但是之后您必须重新连接按钮单击信号,因此您还必须将 stop_thread 方法更改为

    def stop_thread(self):
        del(self.new_thread[-1])
        if self.new_thread: # if no threads left, then no need ro reconnect signal
             self.button_stop_thread.clicked.connect(self.new_thread[-1].stop_process_and_thread)
        print self.new_thread 
    

    至于终止,这可以在 stop_process_and_thread 方法中完成。 (就是你注释掉的那一行,在发出信号之前放好,以防万一)

    现在你的程序应该像你预期的那样运行

    对于您的第一个问题,我不确定线程​​是否已被删除,但现在可以确定它们已被终止。不知何故,在您之前的代码中,您通过将所有变量分配给一个变量而丢失了引用,而其中一些变量仍然处于活动状态。这也回答了你的第二个问题

    至于第三种,qt 文档中不建议以这种方式终止线程。你应该在那里寻找更好、更安全的方法,我也不太确定你使用多处理,但我不能告诉你更多,因为我没有经常使用这个模块。

    我认为您应该改变编程策略。最好在进程内使用线程,而不是像你那样在线程内使用进程

    【讨论】:

    • 感谢您的回答!我已经更新了我的问题中的代码:(1)complex_processing() 做某事,这是一个最小的例子; (2)self.stop_thread_signal.emit()不在if ...之下。
    • 我在我的代码中添加了setTerminationEnabled(True),但它不起作用。我也可以在没有setTerminationEnabled(True) 的情况下终止线程,这不是问题,我无法删除线程对象——这就是问题所在!我知道,我可以手动删除信号连接。我不想手动操作,我想删除线程对象,然后信号连接也必须删除。但它不起作用。
    • 您是否尝试过按照我的建议将线程对象存储在列表中?因为当我这样做时,它们似乎被删除并自动断开连接(如文档中所述),因此它们不会收到任何信号。手动断开连接不在要删除的线程上。在我看来,问题似乎在于将不同的线程对象存储在同一个变量中。
    • 要更好地理解为什么应该使用列表,请仔细查看 stackoverflow.com/questions/986006/… 尤其是第二个答案。尽管您删除了 new_thread 变量,但您之前分配给它的对象可能仍然存在。 new_thread 只是不再引用它们,但它们仍然存在
    • 您的代码有效,因为您手动断开“停止”按钮。如果我手动断开按钮,我的代码也可以工作。我用list 尝试过,我的行为和以前一样。如果我删除列表对象或清除列表,信号不会断开。我想我了解有关数据模型的内容:“对象永远不会被明确销毁;但是,当它们变得无法访问时,它们可能会被垃圾收集。” (docs.python.org/3.4/reference/datamodel.html)。
    【解决方案3】:

    我之前处理过这个问题,并解决了这个问题:

    import time
    from threading import Thread
    
    class myClass(object):
        def __init__(self):
            self._is_exiting = False
    
            class dummpyClass(object):
                pass
            newSelf = dummpyClass()
            newSelf.__dict__ = self.__dict__
    
            self._worker_thread = Thread(target=self._worker, args=(newSelf,), daemon=True)
    
        @classmethod
        def _worker(cls, self):
            i = 0
            while not self._is_exiting:
                print('Loop #{}'.format(i))
                i += 1
                time.sleep(3)
        def __del__(self):
            self._is_exiting = True
            self._worker_thread.join()
    

    dummpyClass 的使用允许 worker 访问我们主类的所有属性,但它实际上并不指向它。因此,当我们删除主类时,它可能会被完全取消引用。

    test = myClass()
    test._worker_thread.start()
    

    请注意,我们的工作人员开始打印输出。

    del test
    

    工作线程已停止。

    解决这个问题的一种更复杂的方法是首先定义一个基类(称为 myClass_base),它存储所有数据和所需的方法,但不支持线程。然后在新类中添加线程支持。

    import time
    from threading import Thread
    class myClass_base(object):
        def __init__(self, specialValue):
            self.value = specialValue
        def myFun(self, x):
            return self.value + x
    
    class myClass(myClass_base):
        def __init__(self, *args, **kwargs):
    
            super(myClass, self).__init__(*args, **kwargs)
    
            newSelf = myClass_base.__new__(myClass_base)
            newSelf.__dict__ = self.__dict__
    
            self._is_exiting = False
    
            self.work_to_do = []
    
            self._worker_thread = Thread(target=self._worker, args=(newSelf,), daemon=True)
    
        @classmethod
        def _worker(cls, self):
            i = 0
            while not self._is_exiting:
                if len(self.work_to_do) > 0:
                    x = self.work_to_do.pop()
                    print('Processing {}.  Result = {}'.format(x, self.myFun(x)))
                else:
                    print('Loop #{}, no work to do!'.format(i))
                i += 1
                time.sleep(2)
    
        def __del__(self):
            self._is_exiting = True
            self._worker_thread.join()
    

    要启动此类,请执行:

    test = myClass(3)
    test.work_to_do+=[1,2,3,4]
    test._worker_thread.start()
    

    请注意,结果开始打印出来。删除工作与以前相同。执行

    del test
    

    线程正常退出。

    【讨论】:

      猜你喜欢
      • 2012-04-11
      • 2018-03-31
      • 2012-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多