简体   繁体   English

Python / PySide:如何销毁终止的线程对象?

[英]Python/PySide: How can i destroy a terminated thread object?

I would like to implement a button to stop a thread with a process, it works but not as expected: i can't delete the thread object. 我想实现一个按钮来停止带有进程的线程,它可以正常工作,但没有达到预期效果:我无法删除线程对象。 ( EDIT: The reference to the thread object seems to be deleted, but the signals are not disconnected automatically by deleting the thread object, i can access it anyway via the signal.) 编辑:对线程对象的引用似乎已删除,但是通过删除线程对象并不会自动断开信号的连接,我仍然可以通过信号访问它。)

I have a modul with a class thread_worker and a function for complex processing which is running as process: 我有一个带有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)

I have two buttons in my application to create/run and terminate a thread object. 我的应用程序中有两个按钮来创建/运行和终止线程对象。

...
...
# 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)
...
...

If i start and stop the first thread - it works fine and terminate, but if i klick on the 'Stop'-Button again the output is: 如果我启动和停止第一个线程-它可以正常工作并终止,但是如果我再次按下“停止”按钮,则输出为:

Thread 1422117088 is not running!

I don't understand it: the object self.new_thread is deleted by del(self.new_thread) or not? 我不明白: del(self.new_thread)是否删除了对象self.new_thread How can i access this object if it was deleted? 如果删除了该对象,我该如何访问? If i start and stop again a new thread, the output is: 如果我启动并再次停止新线程,输出为:

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

Now i do it again (start and stop), the output is: 现在我再做一次(开始和停止),输出是:

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

and so on... 等等...

First question: I don't understand why the old threads are not deleted? 第一个问题:我不明白为什么不删除旧线程? Why i can access them? 为什么我可以访问它们? I think it is not good: my application crashes at some point, if there are too many threads (not deleted objects) in the background. 我认为这样做不好:如果后台线程过多(未删除对象),我的应用程序有时会崩溃。

Second question: I dont't understand why the signals are not disconnected if i delete the object self.new_thread ? 第二个问题:如果删除对象self.new_thread为什么我的信号不断self.new_thread I don't want to disconnect the signals manually: if i have many signals i can forgot to disconnect some signals. 我不想手动断开信号:如果我有很多信号,我可能会忘记断开某些信号。

Third question: I choose this way to stop a thread with one process. 第三个问题:我选择这种方式来停止一个进程的线程。 Is there another way to do this better? 还有其他更好的方法吗?

UPDATE: 更新:

The thread object appears to be destroyed: 线程对象似乎已损坏:

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

But my signals are not disconnected!? 但是我的信号没有断开!? Here is described that: "A signal-slot connection is removed when either of the objects involved are destroyed." 在此描述:“当所涉及的任何一个对象被破坏时,将删除信号插槽连接。” It does not work in my code. 它在我的代码中不起作用。

You're right, your problem is, that your object is not deleted. 没错,问题是您的对象没有被删除。 You delete only the reference self.new_thread . 您仅删除引用self.new_thread The problem is in this line: 问题在这一行:

self.new_thread = thread_worker(self)

The reason for this is that the parent self of the thread is alive. 这样做的原因是线程的父self是活动的。 As long as parent stays alive, the object self.new_thread is not destroyed. 只要父代保持生命,对象self.new_thread就不会被破坏。 Try something like this: 尝试这样的事情:

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

And now you also delete the parent self.threadParent : 现在,您还删除了父self.threadParent

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

Your signals should be disconnected now. 您的信号现在应该断开。

You don't need the following line, because the object self.new_thread has been already deleted after you emit the signal stop_thread_signal : 您不需要以下行,因为发出信号stop_thread_signal后,对象self.new_thread已被删除:

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

This happens possibly because your process doesn't contain anything and finishes after it starts.Therefore the code in 发生这种情况可能是因为您的进程不包含任何内容并且在启动后完成了。因此

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?  

after the if self.process.is_alive() is never reached. 永远不会到达if self.process.is_alive()之后。 As a result the stop_thread_signal is never emitted so the thread is neither terminated nor deleted 结果,从不发出stop_thread_signal,因此线程既不终止也不删除

Change your test complex_processing function to 将您的测试complex_processing函数更改为

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

Now to achieve the effect you want you should store threads in a list so start_thread should become 现在要获得所需的效果,您应该将线程存储在列表中,这样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)

Notice here that before setting up the connections to the last thread of the list (your newly added thread) you must first disconnect the button clicked() signal from all slots. 请注意,在建立与列表的最后一个线程(新添加的线程)的连接之前,必须首先从所有插槽断开按钮clicked()信号。 That is because if you start more than one threads the clicked() signal will be connected to the slots of all of them. 这是因为如果启动多个线程,那么clicked()信号将连接到所有线程的插槽。 Therefore stop_process_and_thread and stop_thread will be called as many times as the number of running threads with unpredicted sequence. 因此, stop_process_and_threadstop_thread的调用次数将与运行顺序意外的线程数相同。 By disconnecting the signal only the last started thread is terminated and removed from the list. 通过断开信号,只有最后启动的线程被终止并从列表中删除。 Also be sure that setTerminationEnabled is set 还要确保设置了setTerminationEnabled

However after that you have to reconnect the button clicked signal, so you also have to change your stop_thread method to 但是,在那之后,您必须重新连接单击按钮的信号,因此还必须将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 

As for termination this can be done inside the stop_process_and_thread method. 至于终止,可以在stop_process_and_thread方法内完成。 (It's the line you have commented out, place it before emitting the signal just in case) (这是您已注释掉的行,在发出信号之前将其放置以防万一)

Now your program should behave as you expected 现在您的程序应该表现出预期的效果

For your first question I'm not really sure if threads are deleted, but sure now they are terminated. 对于第一个问题,我不确定线程​​是否已删除,但现在可以确定它们是否已终止。 Somehow in your previous code you were losing the reference by assigning all to one variable while some of them were still active. 在您先前的代码中,您不知何故通过将全部分配给一个变量而丢失了引用,而其中一些变量仍处于活动状态。 This answers your 2nd question too 这也回答了您的第二个问题

As for the third, terminating a thread this way is not recommended in the qt documentation. 至于第三个,在qt文档中不建议以这种方式终止线程。 you should search there for better and safer ways and I'm also not very sure about your use of multiprocessing but I can't tell you more because I havent used this module a lot. 您应该在那儿搜索更好,更安全的方法,我也不确定您是否使用了多处理,但是由于我没有太多使用此模块,所以我无法告诉您更多信息。

I believe that you should change strategy for programming this. 我相信您应该更改对此编程的策略。 Better use threads inside processes and not processes inside threads as you do 更好地在进程内部使用线程,而不要像在进程中那样在线程内部使用进程

I have dealt with this problem before and have solved it like this: 我之前已经处理过这个问题,并且已经这样解决了:

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()

The use of dummpyClass allows the worker to access all of the attributes of our main class, but it doesn't actually point to it. 使用dummpyClass可以使工作人员访问我们主类的所有属性,但实际上并没有指向它。 Thus, when we delete our main class, it is possible for it to be fully de-referenced. 因此,当我们删除主类时,可能会完全取消引用它。

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

Note that our worker starts printing output. 请注意,我们的工作人员开始打印输出。

del test

Worker thread has stopped. 工作线程已停止。

A more complex way of solving this problem that would allow you access to methods myClass would be to first define a base class (call it myClass_base) that stores all data and required methods, but no threading support. 解决此问题(允许您访问方法myClass的方法)的更复杂方法是​​,首先定义一个基类(称为myClass_base),该基类存储所有数据和所需的方法,但不提供线程支持。 Then add on the threading support in a new class. 然后在新类中添加线程支持。

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()

To start this class execute: 要开始此类,请执行:

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

And note that results start printing out. 并注意结果开始打印出来。 Deleting works the same as before. 删除工作与以前相同。 execute 执行

del test

and the thread properly exits. 并且线程正确退出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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