2

I am trying to run a process in a separate thread but it is freezing my Gui and I cant understand why.

I am initialising the thread in the init function of my class:

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

The encrypt method of the cipher class is:

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))
user2145312
  • 896
  • 3
  • 10
  • 33
  • 1
    It probably won't make any difference, but try to eliminate the `lambda` - e.g. pass the arguments to the `Cipher` constructor and connect the signal directly to `self.cipher.encrypt` (which should be decorated with `@QtCore.Slot`). If that has no effect, it could just be that you are running into issues with the python GIL. In which case, you may need to switch to [multiprocessing](https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing). – ekhumoro Oct 15 '15 at 15:59
  • I see that you've added some more code to your question, but it is really of no use because it is not a runnable test case. You need to *drastically* reduce the amount of code to the point where you can identify the specific sections that are blocking. This will allow other people to run your test case and at least see if they can reproduce the issue. – ekhumoro Oct 20 '15 at 15:33
  • yes, after some reading Im pretty sure you were correct that I am running into issues with the GIL because of what I am trying to achieve in the subthread, so have had success so far with multiprocessing – user2145312 Oct 20 '15 at 15:46
  • @ekhumoro I had a similar issue and I tried running it without the **lambda** function and connecting the signal with **Slot** which resolved the issue. Without the utilization of the **Slot** the only why seems to be subclassing or multiprocessing. I thought this might be helpful even though I ran into the same issue years later. – Vesnog Jan 27 '19 at 16:05

2 Answers2

1

The issue you are experiencing is that the function you are connecting to the started signal is not run in the thread, it's run in the context of where it was set, which seems to be your UI thread.

Normally you would want to create a custom class which inherits from QThread, and any code that you want to be executed would be in the run() function of that class. Like so:

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

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

If that seems like overkill you could just set the value of self.cipher_thread.run to your own function. Here's an example:

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()
smont
  • 1,348
  • 1
  • 12
  • 20
  • While I agree this would result in a suitable result, is it not widely acknowledged as the incorrect use of a QThread - https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/ – user2145312 Oct 16 '15 at 16:41
  • interesting article I found - http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/ ... possibly not so 'incorrect' – user2145312 Oct 16 '15 at 16:50
  • Interesting articles, thanks for posting them. Any luck with getting it to work? – smont Oct 16 '15 at 17:48
  • No unfortunately not. Both of your methods and the original are still locking the GUI. I have eliminated the lambda as ekhumoro suggested but still no luck – user2145312 Oct 16 '15 at 17:49
  • the process that i want to run in the thread is sending signals back to the gui class which is going to update a progress bar...would this be causing issues? – user2145312 Oct 16 '15 at 17:50
  • It shouldn't. I did answer a very similar question about updating a progress bar in a thread, take a look: http://stackoverflow.com/questions/20657753/python-pyside-and-progress-bar-threading/20661135#20661135 – smont Oct 19 '15 at 12:31
  • thanks, i dont think this solves much as Im already handling the signals slots ok I think. I have updated the question to show the encrypt method which is called when the QThread starts – user2145312 Oct 19 '15 at 15:44
  • @user2145312. You really need to provide an [MCVE](http://stackoverflow.com/help/mcve), otherwise this is all just guesswork. – ekhumoro Oct 20 '15 at 15:46
0

As Ekhumoro suggested, I was running into issues with the GIL. using the Multiprocessing module has worked for me.

user2145312
  • 896
  • 3
  • 10
  • 33