我怎么能在开发这么晚的时候线程化这个代码呢?

2024-05-02 17:47:20 发布

您现在位置:Python中文网/ 问答频道 /正文

我一直在为我正在研究的一个遗传算法制作GUI,我犯了一个错误,因为我不知道(现在仍然不知道)如何去做,所以才这么晚才离开线程。因此,基本上,当单击start按钮时,函数run启动整个无限循环过程,这实际上发生在generation\u循环中。每一代循环都会检查它是否仍在运行。其思想是,如果单击了停止或暂停按钮,它将停止循环(使用停止按钮,所有数据将被清除,而使用暂停按钮,它将保留,取消暂停按钮只是将运行设置为True并调用生成\u循环)

因此,我需要找到一种方法,使我的GUI在第1代循环运行时具有响应性。这是我的代码,我尽量减少它,但我不确定什么是线程的重要信息:

class Window(main_window, QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        main_window.__init__(self)
        self.setupUi(self)

        self.scene = QGraphicsScene()
        self.im_view.setScene(self.scene)
        self.setWindowTitle('Fantasy Generator')
        self.running = False
        self.first_run = True
        self.im = Image.new('RGBA', (400, 400), (0, 0, 0, 255))
        self.saved_gens = deque([('A', self.im, self.im, self.im)])
        self.set_save_amount(self.sb_saveamt.value())
        self.population = []

        self.btn_exit.clicked.connect(self.close)
        self.actionQuit.triggered.connect(self.close)
        self.btn_pauser.clicked.connect(self.pause_button)
        self.sb_saveamt.valueChanged[int].connect(self.set_save_amount)
        self.btn_restart.clicked.connect(self.start_button)
        self.btn_loadimage.clicked.connect(self.get_image)
        self.actionLoad_Image.triggered.connect(self.get_image)
        self.gen_sldr.valueChanged[int].connect(self.display_gen)
        self.cb_display.currentIndexChanged.connect(self.change_quality)

        self.has_image = True
        self.display_gen(0)

    def get_image(self):
        pass
        # To save you time I removed the code here. It just sets self.im using a file dialog basically

    def set_save_amount(self, amt):
        if amt == -1:
            self.saved_gens = deque(self.saved_gens)
        else:
            self.saved_gens = deque(self.saved_gens, amt + 1)

    def pause_button(self):
        if self.first_run:
            self.run()
        elif self.running:
            self.running = False
            self.btn_pauser.setText('Resume Execution')
            # pause stuff goes here
        else:
            self.running = True
            self.btn_pauser.setText('Pause Execution')
            self.generation_loop()
            # resume from pause stuff goes here

    def start_button(self):
        if self.first_run:
            self.run()
        else:
            self.end()

# The run function should start the actual process
    def run(self):
        self.btn_restart.setText('End')
        self.btn_pauser.setText('Pause Execution')
        self.first_run = False
        self.running = True

        settings = dict(ind_per_gen=self.sb_ipg.value(), shapes_per_im=self.sb_spi.value(),
                        complexity=self.sb_complexity.value(), mut_rate=self.sb_mutation.value(),
                        cross_chance=self.sb_cross.value(), seed=self.sb_seed.value())
        self.population = Population(self.im, **settings)
        self.generation_loop()

# This is the loop I want to be able to exit out of using buttons
    def generation_loop(self):
        while self.running:
            if self.first_run:
                break
             self.add_generation_data(self.population.next_gen())

    def end(self):
        self.btn_restart.setText('Start')
        self.btn_pauser.setText('Start Execution')
        self.first_run = True
        self.running = False

        self.saved_gens = deque([('A', self.im, self.im, self.im)])
        self.set_save_amount()
        self.display_gen(0)

    def add_generation_data(self, data):
        self.saved_gens.append(data)
        self.gen_sldr.setMaximum(len(self.saved_gens) - 1)
        self.gen_sldr.setValue(len(self.saved_gens) - 1)
        self.display_gen(data[0] + 1)

    def change_quality(self):
        self.display_gen(self.gen_sldr.value())

    def resizeEvent(self, e):
        if self.has_image:
            self.im_view.fitInView(QRectF(0, 0, self.width, self.height), Qt.KeepAspectRatio)
            self.scene.update()

    def display_image(self, image):
        self.scene.clear()
        if image.mode != 'RGBA':
            image = image.convert('RGBA')
        self.width, self.height = image.size
        qim = ImageQt.ImageQt(image)
        pixmap = QPixmap.fromImage(qim)
        self.scene.addPixmap(pixmap)
        self.im_view.fitInView(QRectF(0, 0, self.width, self.height), Qt.KeepAspectRatio)
        self.scene.update()

    def display_gen(self, index):
        self.lcd_cur_gen.display(self.saved_gens[index][0])

        if self.cb_display.currentIndex() == 0:
            self.display_image(self.saved_gens[index][1])
        elif self.cb_display.currentIndex() == 1:
            self.display_image(self.saved_gens[index][2])
        else:
            self.display_image(self.saved_gens[index][3])



if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

编辑:我也刚刚发现,我甚至不能改变图形视图从生成循环,但它的工作和变化,如果我限制循环


Tags: runimageselfifvaluedefconnectdisplay
2条回答

您可以在这里使用线程事件。你知道吗

from threading import Thread, Event

一旦你检测到按钮点击

class MyThread(Thread):
    def __init__(self, the_function, <any input param you want to provide>):
            Thread.__init__(self)
            self.stop_event = Event()
            self.exec_func = the_function

    def set_stop_flag(self, value):

       if value:
           self.stop_event.set()
       else:
           self.stop_event.clear()

    def run(self):
        while True:
            try:
                if not self.stop_event.is_set()
                    self.exec_func()
                else:
                    break # once the event is set, you can break which will kill this thread.
                # To stop busy waiting you can make this thread sleep for some seconds after each cycle.
                import time
                time.sleep(10) # 10 seconds wait before the next cycle.
            except Exception, excep:
                print "Got exception > ", str(excep)

现在,在您的代码中嵌入此代码段并保留此线程的引用。 比如说

self.my_thread = MyThread(self.function_to_perform, <blabla>)
self.my_thread.setDaemon(True) # So that you don't have to worry about it when the Main process dies!
self.my_thread.start()

现在,一旦你得到一个停止按钮,点击你调用的事件

self.my_thread.set_stop_flag(True) # Bingo! Your thread shall quit.

为了将长时间运行的代码移动到线程中,您需要首先确定长时间运行的代码的哪些部分与GUI交互,哪些部分不与GUI交互。这样做的关键原因是禁止从辅助线程与GUI交互,这将导致错误。你知道吗

它看起来像self.population.next_gen()是代码的长时间运行位,并且不与GUI交互(尽管没有提供这样做的功能,所以我不能确定),而self.add_generation_data(...)更新GUI应该相当快。你知道吗

因此,这使得分离变得相当简单,我将在下面展示。你知道吗

现在,关于线程。Python通过threading模块提供线程(如其他答案所示),但是如果希望线程与GUI有任何关系,则不建议将这些线程用于PyQt应用程序(请参见here)。PyQt还通过QThread对象提供线程,该对象集成了对发送和接收Qt信号(线程安全)的支持。简而言之,QThread有一个单独的事件循环,并处理异步接收到的信号到主线程,从而将事件循环留在主线程中处理GUI事件(如按钮单击)。你知道吗

通常创建一个从QObject继承的新类,实例化它并将其移动到QThread。对象中由信号发射触发的插槽(也称为方法),然后在线程中运行。你知道吗

所以你会想这样做

class MyWorker(QObject):
    done = pyqtSignal(object) # you may need to update "object" to the type returned by Population.next_gen()

    def __init__(self, settings):
        # create the population object with whatever settings you need
        # Note that this method runs in the parent thread as you have 
        # yet to move the object to a new thread. It shouldn't cause any
        # problems, but may depend on what the Population class is/does.

        # TODO: I've removed the reference to an image here...
        #it may or may not be thread safe. I can't tell from your code.
        self.population = Population(..., settings)

    @pyqtSlot()
    def next_gen(self):
        new_gen = self.population.next_gen()
        self.done.emit(new_gen)

class Window(....):
    make_next_generation = pyqtSignal() 
    ....

    def run(self):
        self.btn_restart.setText('End')
        self.btn_pauser.setText('Pause Execution')
        self.first_run = False
        self.running = True

        settings = dict(ind_per_gen=self.sb_ipg.value(), shapes_per_im=self.sb_spi.value(),
                        complexity=self.sb_complexity.value(), mut_rate=self.sb_mutation.value(),
                        cross_chance=self.sb_cross.value(), seed=self.sb_seed.value())

        self.setupThread(settings)

    def setupThread(self, settings):
        self.thread = QThread()
        self.worker = MyWorker(settings)    
        self.worker.moveToThread(self.thread)

        # connect a signal in the main thread, to a slot in the worker. 
        # whenever you emit the signal, a new generation will be generated 
        # in the worker thread
        self.make_next_generation.connect(self.worker.next_gen)

        # connect the signal from the worker, to a slot in the main thread.
        # This allows you to update the GUI when a generation has been made
        self.worker.done.connect(self.process_generation)

        # Start thread
        self.thread.start()  

        # emit the signal to start the process!
        self.make_next_generation.emit()

    def process_generation(new_gen):
        # run the GUI side of the code
        # ignore the new generation if the "end" button was clicked
        if not self.first_run:
            self.add_generation_data(new_gen)

        if self.running:
            # make another generation in the thread!
            self.make_next_generation.emit()


    def pause_button(self):
        if self.first_run:
            self.run()
        elif self.running:
            self.running = False
            self.btn_pauser.setText('Resume Execution')
            # pause stuff goes here
        else:
            self.running = True
            self.btn_pauser.setText('Pause Execution')

            # make another generation in the thread!
            self.make_next_generation.emit()

注意事项:

  • 我的答案中没有包括你所有的代码。酌情合并。你知道吗
  • 我不确定是什么自我im是。它被传递给Population,因此在代码中可能存在一些我看不到的线程不安全行为。我把它交给你修理
  • 我熟悉PyQt4,而不是PyQt5,所以有可能我做的一些事情不太对劲。您应该可以很容易地从任何引发的错误消息中找出要更改的内容。你知道吗
  • 每次从头开始重新创建线程和工作线程都有点麻烦。您可能需要考虑将Population的实例化移到worker中的一个方法(一个不是__init__的方法)中,并在每次您希望从头开始时调用它(就像我们触发新一代一样)。这将允许您将几乎所有的setupThread移动到Window.__init__方法,然后当单击开始按钮时,您只需发出一个信号来重新创建Population,然后是一个信号来生成第一代。你知道吗

相关问题 更多 >