V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
falseen
V2EX  ›  Python

关于 pyqt5 跨进程操作 ui 的问题。

  •  
  •   falseen · 2017-03-12 18:26:48 +08:00 · 8757 次点击
    这是一个创建于 2811 天前的主题,其中的信息可能已经有所发展或是发生改变。

    是这样的,我用 pyqt5 写了一个 gui 程序,在主线程中用 multiprocessing 启动新的进程执行一些任务。现在我想把这个子进程的 log 输出到 ui 上,采用了添加 logging.Handler 的方式获取子进程的 log ,然后写入到 ui 中。但是问题来了,由于 qt 的进程安全机制,明明已经获取到 log 了,但是它就是不让我输出到 ui 中,通过 print 的方式是可以显示的。

    所以想问下各位大神,我需要怎么做才能让它把 log 写到 ui 中?

    代码大概是这样的:

    class MyLogHandler(logging.Handler):
        def __init__(self, obj):
            logging.Handler.__init__(self)
            self.Object = obj
    
        def emit(self, record):
            if record.levelno<self.level: return
            tstr = time.strftime('%Y-%m-%d %H:%M:%S.%U')
            self.Object.append("[%s][%s] %s"%(tstr, record.levelname, record.getMessage()))
            self.Object.moveCursor(QtGui.QTextCursor.End)
    
    
    mySW = MainWindow()
    handler = MyLogHandler(mySW.loggingBrowser)
    logging.getLogger().addHandler(handler)
    multiprocessing.Process(pass).start()
    

    其中的 loggingBrowser就是 log 显示组件。由于 logging 是模块级别的,因此主线程中的MyLogHandler 可以捕获到子进程的 log 输出。

    补充:

    当然,如果用多线程的话是可以的,但是不管是python的多线程还是qt的多线程,都存在无法强制结束子线程的问题。所以只能用多进程了。

    20 条回复    2017-03-14 16:30:00 +08:00
    XYxe
        1
    XYxe  
       2017-03-12 20:22:38 +08:00
    用信号不行吗?
    signal = pyqtSignal(str)
    signal.connect(log_func) # 主线程
    signal.emit(log_content) # 子线程
    nyanyh
        2
    nyanyh  
       2017-03-12 20:23:32 +08:00
    1L 正解
    wwqgtxx
        3
    wwqgtxx  
       2017-03-12 20:28:06 +08:00
    根本就不是这个问题,在 multiprocessing 开启的子进程和父进程之间是隔离的,你所说的“由于 logging 是模块级别的,因此主线程中的 MyLogHandler 可以捕获到子进程的 log 输出。”这句话本身就不成立,你用 print 能看到输出是因为这个是子进程直接输出到 stdout 了,根本就不是你父进程捕获到的
    你要想解决这个问题,需要通过进程间通讯来传递你的日志信息
    falseen
        4
    falseen  
    OP
       2017-03-12 21:07:16 +08:00
    @XYxe 试了不行,难道是我姿势不对 ?

    修改之后的代码大概是这个样子的:

    ```python

    class MyLogHandler(logging.Handler):
    def __init__(self, obj):
    logging.Handler.__init__(self)
    self.Object = obj


    def emit(self, record):
    if record.levelno<self.level: return
    tstr = time.strftime('%Y-%m-%d %H:%M:%S.%U')
    self.Object.sin.emit("[%s][%s] %s" %(tstr, record.levelname, record.getMessage()))
    self.Object.loggingBrowser.moveCursor(QtGui.QTextCursor.End)

    class MainWindow(QMainWindow, Ui_MainWindow):
    sin = pyqtSignal(str)
    def __init__(self, parent=None):
    super(MainWindow, self).__init__(parent)
    self.setupUi(self)
    self.sin.connect(self.loggingBrowser.append)
    handler = MyLogHandler(mySW.loggingBrowser)
    logging.getLogger().addHandler(handler)
    ........
    multiprocessing.Process(pass).start()


    mySW = MainWindow()


    ```
    falseen
        5
    falseen  
    OP
       2017-03-12 21:10:10 +08:00
    手误,打错了。
    handler = MyLogHandler(mySW.loggingBrowser) 这一行应该是 handler = MyLogHandler(mySW)
    wwqgtxx
        6
    wwqgtxx  
       2017-03-12 22:04:39 +08:00
    @XYxe pyqt 的 Signal 只是能跨线程,不能跨进程传输吧
    XYxe
        7
    XYxe  
       2017-03-12 22:39:03 +08:00
    @wwqgtxx 啊,应该是的。

    @falseen 在主进程里面创建一个线程用来和其他进程通信,用 Queue 。然后主进程内部用 pyqtSignal 。

    weyou
        8
    weyou  
       2017-03-12 22:44:23 +08:00 via Android
    @wwqgtxx 是正解
    falseen
        9
    falseen  
    OP
       2017-03-12 23:01:44 +08:00
    @wwqgtxx 是的,如你所说。是我理解错了。看来只能用进程间通信了。。
    wwqgtxx
        10
    wwqgtxx  
       2017-03-12 23:07:25 +08:00
    @falseen 最简单的方法就是用 @XYxe 的方法,而跨进程 Queue 可以使用 multiprocessing.Queue 来实现
    wwqgtxx
        11
    wwqgtxx  
       2017-03-12 23:11:59 +08:00
    不过要注意 multiprocessing 类库中提供的跨进程通讯代理类的使用有一个小问题,就是不能反复创建新的线程来调用这些代理方法,否则会导致他内部创建过多的 socket 连接,最后被操作系统 kill 掉
    falseen
        12
    falseen  
    OP
       2017-03-12 23:15:36 +08:00
    @wwqgtxx @XYxe 如果用进程间通信的话,感觉还不如用多进程。想请教下两位大神,如何强制关闭 Qthread 或 threading 生成的线程? 我试过 Qthread 的 terminate() wait() , wait() 之后就会无限等待,根本不会结束。因为我要启动的子线程是不可控的,因此不可能通过修改子线程的源码来自动退出。只能是由主线程结束。
    wwqgtxx
        13
    wwqgtxx  
       2017-03-12 23:18:54 +08:00
    @falseen 我记得是可以通过 cytpes 调用 cpython 的 c 函数使得在另外一个进程中强行抛出一个 BaseExecption ,具体的办法你可以自己查查
    wwqgtxx
        14
    wwqgtxx  
       2017-03-12 23:22:11 +08:00
    https://www.oschina.net/question/172446_2159505
    这篇文章你可以参考参考
    toono
        15
    toono  
       2017-03-13 08:30:03 +08:00
    差点大意了 ,果然是进程间通信
    falseen
        16
    falseen  
    OP
       2017-03-13 10:15:10 +08:00 via Android
    @wwqgtxx 试了没效果。我感觉如果用进程间通信的话还不如把 log 写入文件,然后主进程去读取。反正都要保存 log 的。
    wwqgtxx
        17
    wwqgtxx  
       2017-03-13 10:50:13 +08:00 via iPhone
    @falseen 如果多进程去读写文件容易出更多的奇奇怪怪的坑,具体的你自己衡量吧
    wwqgtxx
        18
    wwqgtxx  
       2017-03-13 10:51:25 +08:00 via iPhone
    对了,我前面说的那个强行抛异常只适用于多线程环境,不能跨进程抛异常
    falseen
        19
    falseen  
    OP
       2017-03-14 15:58:41 +08:00
    @wwqgtxx
    用了进程间通信之后最终还是决定使用写入文件的方式,主进程和子进程程都把 log 写入文件,需要查看的 log 的时候开一个子线程读取然后显示。我觉得这样开销会小一点。

    另外想问下大神,如何优化 pyqt5 的内存占用呢 ?具体见这个帖子 https://www.v2ex.com/t/347235
    wwqgtxx
        20
    wwqgtxx  
       2017-03-14 16:30:00 +08:00 via iPhone
    @falseen 那你最好注意不要在主进程和子进程中同时写文件,否则…
    pyqt5 我没仔细研究过,所以帮不上你
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1552 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 17:05 · PVG 01:05 · LAX 09:05 · JFK 12:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.