Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 我怎么能在开发这么晚的时候线程化这段代码呢?_Python_Multithreading_Python 3.x_Pyqt_Pyqt5 - Fatal编程技术网

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

Python 我怎么能在开发这么晚的时候线程化这段代码呢?,python,multithreading,python-3.x,pyqt,pyqt5,Python,Multithreading,Python 3.x,Pyqt,Pyqt5,我一直在为我正在研究的遗传算法制作GUI,我犯了这么晚才离开线程的错误,因为我不知道(现在仍然不知道)如何去做。因此,本质上,当单击开始按钮时,函数“run”启动整个无限循环过程,这实际上发生在生成循环中。每一代循环都会检查它是否仍在运行。其想法是,如果单击停止或暂停按钮,它将停止循环(使用停止按钮,所有数据将被清除,使用暂停按钮,它将保留,取消暂停按钮仅将运行设置为True,并调用生成循环) 因此,我需要找到一种方法,使我的GUI在第_代循环运行时能够响应。这是我的代码,我试图将其最小化,但我

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

因此,我需要找到一种方法,使我的GUI在第_代循环运行时能够响应。这是我的代码,我试图将其最小化,但我不确定线程的重要信息是什么:

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

编辑:我还刚刚发现,我甚至无法在generation_循环中更改图形视图,但如果我限制循环,它会工作并更改。您可以在此处使用线程事件

从线程导入线程,事件

一旦你检测到按钮点击

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)

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

它看起来像是
self.population.next\u gen()
是代码的长时间运行部分,在
self.add\u generation\u data(…)
更新GUI时不会与GUI交互(虽然没有提供,所以我不能确定)

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

现在,关于线程。Python通过
threading
模块提供线程(如其他答案所示),但是如果希望线程与GUI有任何关系,则不建议将这些线程用于PyQt应用程序(请参阅)。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()
注意事项:

  • 我的答案中没有包括你所有的代码。酌情合并
  • 我不确定self.im是什么。它被传递给
    群体
    ,因此您的代码中可能存在一些我看不到的线程不安全行为。我把它留给你来修理
  • 我熟悉PyQt4,而不是PyQt5,所以我做的一些事情可能不太正确。您应该可以很容易地从引发的任何错误消息中找出要更改的内容
  • 每次从头开始重新创建线程和工作线程都有点混乱。您可能需要考虑将<代码>人口< /代码>的实例化移动到Word中的一个方法(一个不是代码>yiNITSy< < /Cord>),并且每次您想从头开始(我们以触发新一代的方式)调用它。。这将允许您将几乎所有的
    setupThread
    移动到
    窗口。\uuuu init\uuuu
    方法,然后单击开始按钮时,您只需发出一个信号来重新创建
    填充
    ,然后再发送一个信号来生成第一代

我不确定这是否适用于我。我需要能够停止进程的执行,但稍后从停止状态或调用end后的状态重新启动它。用户也必须是启动、停止和暂停它的人。我正在寻找一种方法,使用我使用的按钮设置窗口运行和首次运行标志veOk然后简单地设置事件并在执行过程中检查是否。不要中断会终止线程的循环。对于用户启动它,您可以将线程的执行挂接到用户启动按钮单击事件。请参考此答案,结合这两种解决方案可以帮到您。我似乎无法使任何一种方法起作用。我尝试了运行该链接中看到的事件,然后使用相同类型的结构,但它对我不起作用。循环只是运行,不允许我做任何事情来清除标记。这将不起作用,因为长时间运行的函数和GUI更新之间存在耦合,这无法从辅助线程完成(PyQt禁止)。虽然如果将执行的
self.function____执行
仅指定为填充生成,则应该是线程安全的,但在GUI和线程之间没有简单、线程安全的方式进行通信,以便像原始代码那样继续更新GUI。这就是PyQt提供与sig集成的
QThread
的原因Qt的nal/slot机制,以便于在线程之间安全地发送消息。@three_Pinepples很高兴知道这一点。谢谢。我意识到这并没有多大帮助,但当前的答案似乎是从Python的角度而不是PyQt的角度编写的,这使得它们的答案相当糟糕。至少其中一个答案不是正确的阅读安全,因为它从辅助线程调用PyQt方法。我现在没有时间提供答案,但如果没有其他人提供答案,我会尝试返回到它。我不知道我是否可以这样回答,但我已经花了很多时间与此程序斗争,它不起作用,所以以防万一你忘了或我会忘记什么我想看看你是否还活着
self.my_thread.set_stop_flag(True) # Bingo! Your thread shall quit.
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()