【问题标题】:How to integrate killable processes/thread in Python GUI?如何在 Python GUI 中集成可终止的进程/线程?
【发布时间】:2015-12-15 08:34:46
【问题描述】:

大家好,我真的是 python 新手,我面临一个我无法完全掌握的任务。

我用 Tkinter 创建了一个界面,它应该可以完成一些看似简单的壮举。 通过单击“开始”按钮,将启动两个线程/进程(每个线程/进程调用多个子函数),它们主要从串行端口(每个进程一个端口)读取数据并将它们写入文件。 I/O 操作在一个带有非常高计数器的 while 循环中循环,以允许它们几乎无限期地继续进行。

“停止”按钮应该会停止采集,并且基本上应该:​​

  1. 杀死读/写线程
  2. 关闭文件
  3. 关闭串口

不幸的是,我仍然不明白如何完成第 1 点,即:如何在不杀死整个 GUI 的情况下创建可杀死线程。有没有办法做到这一点?

谢谢大家!

【问题讨论】:

    标签: python multithreading kill-process


    【解决方案1】:

    首先,您必须选择是使用线程还是进程。

    我不会过多讨论差异,google it ;) 无论如何,这里有一些事情需要考虑:在线程之间建立通信比在进程之间建立通信要容易得多;在 Python 中,所有线程都将在同一个 CPU 内核上运行(参见 Python GIL),但子进程可能使用多个内核。

    流程

    如果您使用子流程,有两种方法:subprocess.Popenmultiprocessing.Process。使用Popen,您可以运行任何东西,而Process 提供了一个更简单的类似线程的接口来运行python 代码,它是您项目中子进程的一部分。

    两者都可以使用terminate 方法杀死。

    请参阅 multiprocessingsubprocess 的文档

    当然,如果你想要一个更优雅的退出,你会想要向子进程发送一个“退出”消息,而不是仅仅终止它,以便它有机会进行清理。你可以这样做,例如通过写入其标准输入。该进程应该从标准输入读取,当它收到“退出”消息时,它应该在退出之前做任何你需要的事情。

    线程

    对于线程,你必须实现自己的停止机制,而不是使用像process.terminate() 这样暴力的东西。

    通常,一个线程在一个循环中运行,并在该循环中检查一个显示 stop 的标志。然后你跳出循环。

    我通常是这样的:

    class MyThread(Thread):
        def __init__(self):
            super(Thread, self).__init__()
            self._stop_event = threading.Event()
    
        def run(self):
            while not self._stop_event.is_set():
                # do something
                self._stop_event.wait(SLEEP_TIME)
            # clean-up before exit
    
        def stop(self, timeout):
            self._stop_event.set()
            self.join(timeout)
    

    当然,你需要一些异常处理等,但这是基本思想。

    编辑:评论中问题的答案

    thread.start_new_thread(your_function) 启动一个新线程,这是正确的。另一方面,threading 模块为您提供了更高级的 API。

    使用threading 模块,您可以使用:

    t = threading.Thread(target=your_function)
    t.start()
    

    或者您可以创建自己的类,该类继承自Thread,并将您的功能放入run 方法中,如上例所示。然后,当用户单击开始按钮时,您可以:

    t = MyThread()
    t.start()
    

    您应该将 t 变量存储在某处。确切的位置取决于您如何设计应用程序的其余部分。我可能会有一些对象将所有活动线程保存在一个列表中。

    当用户点击停止时,您应该:

    t.stop(some_reasonable_time_in_which_the_thread_should_stop)
    

    之后,您可以从列表中删除t,它不再可用。

    【讨论】:

    • 感谢您的广泛回答!我想我错过了线程的要点。在我的例子中,带有 while 循环的函数是由thread.start_new_thread(function_with_while) 在线程中生成的,但我应该循环 thread 而不是函数本身。那是对的吗?在这种情况下,我如何将停止标志传递给线程?线程将在按下“开始”按钮时产生,但停止由另一个 Tkinter 对象(“停止”按钮)处理。使线程全局化就足够了吗?
    • 谢谢,但关键是当用户点击停止时,线程应该意识到已经引发了停止标志并停止,否则继续在串行端口上获取。没有“合理的停止时间”,因为除非另有说明,否则它应该继续获取。那是我觉得很难的部分,它不允许我在获取步骤中插入“睡眠”点,否则我会丢失港口的包裹......
    • stop 的 timout 参数是可选的。您可以将其设置为None。请参阅Thread.join() 的文档。如果你很好地实现了你的 run 方法,这将立即停止。超时的原因是您可以处理线程问题。例如。如果你在run 中有一个错误导致它永远不会停止,调用stop(它调用join)将冻结主线程。超时告诉函数何时放弃是线程没有响应。在这种情况下,您要做什么再次取决于您。
    • 谢谢,很清楚,事实上这正是正在发生的事情:我将线程实现为线程类(即:MyThread(threading.Thread))并定义了一个连接属性(在 Python 中是这样调用的吗? ): timeout 是 none 并且当我单击“Stop”按钮并停止线程时整个 GUI 冻结...
    • Thread.join() 做的不多。它只是意味着“等待这个线程完成”。它不会导致线程停止。您必须告诉它停止,然后等待它停止。顺便说一句,这是一个很好的解释为什么它需要 timeout 参数 - 意思是“等待这个线程完成,但不要等待超过 X 秒
    【解决方案2】:

    首先您可以使用subprocess.Popen() 生成子进程,然后您可以使用Popen.terminate() 终止它们。

    请注意,如果您愿意,您也可以在单个 Python 线程中完成所有操作,无需子进程。在单个事件循环中从多个端口“多路复用”读取是完全可能的。

    【讨论】:

      猜你喜欢
      • 2018-08-13
      • 2021-07-12
      • 1970-01-01
      • 2015-04-23
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 2016-01-12
      相关资源
      最近更新 更多