Qthread 锁定 Gui PySide

Posted

技术标签:

【中文标题】Qthread 锁定 Gui PySide【英文标题】:Qthread locking up Gui PySide 【发布时间】:2015-10-15 11:47:53 【问题描述】:

我试图在单独的线程中运行一个进程,但它冻结了我的 Gui,我不明白为什么。

我正在我班级的 init 函数中初始化线程:

self.cipher = Cipher()
self.cipher_thread = QThread()
self.cipher.moveToThread(self.cipher_thread)

self.cipher_thread.started.connect(lambda: self.cipher.encrypt(self.plaintext_file_path,
                                                                       self.ciphertext_file_path,
                                                                       self.init_vector,
                                                                       self.key))

self.cipher_thread.start()

cipher类的加密方法为:

def encrypt(self):
    # check that both the key and the initialisation vector are 16 bytes long
    if len(self.k) == self.key_byte_length and len(self.init_vector) == self.byte_length:
        if not self.used:
            self.used = True

            # get the padding bytes and store in a list
            self.padding_bytes = self.__getPaddingBytes()

            # generate sub keys
            # initial subkey is first four words from key
            init_subkey_words = []
            for i in range(0, self.key_byte_length-3,4):
                init_subkey_words.append(self.k[i:i+4])

            self.__genSubKeys(init_subkey_words)

            # read file and append the padding to it
            with open(self.plaintext_file_path, 'rb') as f:
                self.plaintext_data = bytearray(f.read())
            self.plaintext_data += self.padding_bytes

            # set total size
            self.total_size_bytes = len(self.plaintext_data)

            # insert the initialisation vector as the first 16 bytes in the ciphertext data
            self.ciphertext_data = self.init_vector

            '''
            begin encryption
            --------------------------------------------------------------------------------------------------------
            '''
            self.start_time = datetime.datetime.now()
            # loop through the file 16 bytes at a time
            for i in range(0, int(len(self.plaintext_data)), self.byte_length):  # i increases by 16 each loop
                # if self.block_time is not None:
                    # print('block time is', datetime.datetime.now()-self.block_time)
                self.block_time = datetime.datetime.now()

                # set the 16 byte state - bytearray Object
                state = copy.deepcopy(self.plaintext_data[i:i+self.byte_length])

                # xor the state with the initialisation vector and first subkey
                for j in range(self.byte_length):
                    state[j] ^= self.init_vector[j]
                    state[j] ^= self.sub_keys[0][j]

                # round start
                # --------------------------------------------------------------------------------------------------
                for j in range(self.num_rounds):
                    self.current_round += 1     # increment current round counter

                    '''
                    arrange the data into a 4x4 matrix
                    [[1, 5, 9, 13],
                    [2, 6, 10, 14],
                    [3, 7, 11, 15],
                    [4, 8, 12, 16]]
                    '''
                    state_matrix = np.array(state)
                    state_matrix.resize(4, 4)
                    state_matrix.swapaxes(0, 1)

                    # byte substitution
                    # ----------------------------------------------------------------------------------------------
                    for row in state_matrix:
                        for byte in row:
                            byte = self.__sBoxSubstitution(byte)

                    # shift row - row k shifts left k places
                    # ----------------------------------------------------------------------------------------------
                    state_matrix = state_matrix.tolist()
                    for row in range(1, 4):
                        for l in range(0, row):
                            state_matrix[row].append(state_matrix[row].pop(0))
                    state_matrix = np.array(state_matrix)


                    # mix column - not included in last round
                    # ----------------------------------------------------------------------------------------------
                    if self.current_round is not self.num_rounds:
                        # swap axes of state matrix
                        state_matrix.swapaxes(0, 1)

                        # create temporary holder for the computed values
                        mixed_col_bytes = [[], [], [], []]

                        for k in range(4):
                            for l in range(4):
                                mixed_col_bytes[k].append(
                                    self.__GFMult(self.MIX_COL_MATRIX[l][0], state_matrix[k][0]) ^
                                    self.__GFMult(self.MIX_COL_MATRIX[l][1], state_matrix[k][1]) ^
                                    self.__GFMult(self.MIX_COL_MATRIX[l][2], state_matrix[k][2]) ^
                                    self.__GFMult(self.MIX_COL_MATRIX[l][3], state_matrix[k][3]))

                        # restore state matrix from temporary holder and swap axes back
                        state_matrix = np.array(copy.deepcopy(mixed_col_bytes))
                        state_matrix.swapaxes(0, 1)

                    # restore single bytearray state
                    state_matrix = state_matrix.flatten()
                    state_matrix = state_matrix.tolist()
                    state = bytearray(state_matrix)

                    # key addition
                    # ----------------------------------------------------------------------------------------------
                    for k in range(self.byte_length):
                        state[k] ^= self.sub_keys[self.current_round][k]

                self.ciphertext_data += state                    # append state to ciphertext data
                self.init_vector = self.ciphertext_data[-16:]    # update the initialisation vector
                self.current_round = 0                           # reset current round number
                self.completed_size_bytes += self.byte_length
                self.percent_done = (self.completed_size_bytes/self.total_size_bytes)*100

                self.updateProgressSig.emit(int(self.percent_done))
            # finish encryption
            self.__saveEncryptedData()
            print('total encryption time:', datetime.datetime.now() - self.start_time)
            # finish
            self.finish(self.ciphertext_file_path)

    # either the key of the initialisation vector are not the correct length
    else:
        print(' either the key length or initialisation vector is the wrong length')
        print('---')
        print('key length:', len(self.k))
        print('iv length:', len(self.init_vector))

【问题讨论】:

它可能不会有任何区别,但请尝试消除 lambda - 例如将参数传递给Cipher 构造函数并将信号直接连接到self.cipher.encrypt(应该用@QtCore.Slot 装饰)。如果这没有效果,那可能只是你遇到了 python GIL 的问题。在这种情况下,您可能需要切换到multiprocessing。 我看到您在问题中添加了更多代码,但这确实没有用,因为它不是可运行的测试用例。您需要大幅将代码量减少到可以识别阻塞的特定部分的程度。这将允许其他人运行您的测试用例并至少查看他们是否可以重现该问题。 是的,经过一番阅读,我很确定您是正确的,因为我试图在子线程中实现的目标,所以我遇到了 GIL 问题,所以到目前为止,多处理取得了成功跨度> @ekhumoro 我有一个类似的问题,我尝试在没有 lambda 函数的情况下运行它,并将信号与 Slot 连接,从而解决了这个问题。如果没有使用 Slot,唯一的原因似乎是子类化或多处理。我认为这可能会有所帮助,即使多年后我遇到了同样的问题。 【参考方案1】:

您遇到的问题是您连接到started 信号的函数没有在线程中运行,而是在设置它的上下文中运行,这似乎是您的 UI 线程。

通常您会想要创建一个继承自 QThread 的自定义类,并且您想要执行的任何代码都将在该类的 run() 函数中。像这样:

class MyTask(QThread):
  def __init__ (self):
    QThread.__init__(self)

  def run(self):
    print("Code to run in the thread goes here.")

如果这看起来有点矫枉过正,您可以将self.cipher_thread.run 的值设置为您自己的函数。这是一个例子:

import time
from PySide.QtCore import QThread
from PySide import QtGui

app = QtGui.QApplication("")

def main():
  task = SomeTask()
  thread = QThread()

  # Just some variables to pass into the task
  a, b, c = (1, 2, 3)
  thread.run = lambda: task.runTask(a, b, c)


  print("Starting thread")
  thread.start()

  # Doing this so the application does not exit while we wait for the thread to complete.
  while not thread.isFinished():
    time.sleep(1)

  print("Thread complete")

class SomeTask():
  def runTask(self, a, b, c):
    print a, b, c
    print("runTask Started")
    time.sleep(5)
    print("runTask Complete")


if __name__ == "__main__":
  main()

【讨论】:

虽然我同意这会产生合适的结果,但它是否没有被广泛认为是不正确使用 QThread - mayaposch.wordpress.com/2011/11/01/… 我发现的有趣文章 - blog.debao.me/2013/08/… ... 可能不是那么“不正确” 有趣的文章,感谢您发布它们。让它工作顺利吗? 不,很遗憾没有。您的方法和原始方法都仍在锁定 GUI。我已经按照 ekhumoro 的建议消除了 lambda,但仍然没有运气 我想在线程中运行的进程正在向 gui 类发送信号,该类将更新进度条...这会导致问题吗?【参考方案2】:

正如 Ekhumoro 所建议的,我遇到了 GIL 的问题。使用多处理模块对我有用。

【讨论】:

以上是关于Qthread 锁定 Gui PySide的主要内容,如果未能解决你的问题,请参考以下文章

如何从 GUI 停止 QThread

QThread:使用 GUI python 进行线程化

QThread 和 GUI 线程说明

当 GUI 冻结时显示带有 QThread 的 QMessageBox

将对象的亲和性从 QThread 更改为主 GUI 线程

使用 QThread 两次单击按钮 2 后 Pyqt GUI 突然没有响应