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