簡體   English   中英

Python / PySide:如何銷毀終止的線程對象?

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

我想實現一個按鈕來停止帶有進程的線程,它可以正常工作,但沒有達到預期效果:我無法刪除線程對象。 編輯:對線程對象的引用似乎已刪除,但是通過刪除線程對象並不會自動斷開信號的連接,我仍然可以通過信號訪問它。)

我有一個帶有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!

我不明白: del(self.new_thread)是否刪除了對象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為什么我的信號不斷self.new_thread 我不想手動斷開信號:如果我有很多信號,我可能會忘記斷開某些信號。

第三個問題:我選擇這種方式來停止一個進程的線程。 還有其他更好的方法嗎?

更新:

線程對象似乎已損壞:

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

但是我的信號沒有斷開!? 在此描述:“當所涉及的任何一個對象被破壞時,將刪除信號插槽連接。” 它在我的代碼中不起作用。

沒錯,問題是您的對象沒有被刪除。 您僅刪除引用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?

發生這種情況可能是因為您的進程不包含任何內容並且在啟動后完成了。因此

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文檔中不建議以這種方式終止線程。 您應該在那兒搜索更好,更安全的方法,我也不確定您是否使用了多處理,但是由於我沒有太多使用此模塊,所以我無法告訴您更多信息。

我相信您應該更改對此編程的策略。 更好地在進程內部使用線程,而不要像在進程中那樣在線程內部使用進程

我之前已經處理過這個問題,並且已經這樣解決了:

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可以使工作人員訪問我們主類的所有屬性,但實際上並沒有指向它。 因此,當我們刪除主類時,可能會完全取消引用它。

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

請注意,我們的工作人員開始打印輸出。

del test

工作線程已停止。

解決此問題(允許您訪問方法myClass的方法)的更復雜方法是​​,首先定義一個基類(稱為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

並且線程正確退出。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM