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
hakono
V2EX  ›  Python

QT 开发 UI 时,在子线程创建界面,主线程处理逻辑,两者之间通过一个消息队列链接的想法是否可取?

  •  
  •   hakono · 2018-11-08 08:57:27 +08:00 · 8137 次点击
    这是一个创建于 2241 天前的主题,其中的信息可能已经有所发展或是发生改变。

    刚接触 UI 开发没多久,很多东西还不知道 用的 pyqt5,一般创建界面都是在主线程里创建,遇到耗时的任务时开个 QThread 子线程来处理不是吗。

    我想要达到分离界面和逻辑的目的,可能我水平次,所以能想到的解决办法就是: 在子线程里创建主界面,主线程里处理逻辑。 然后两者通过一个 Queue 消息队列链接。比如:界面输入完数据后,往 queue 里放一个自定义消息 MSG_BTN_CLICK,然后主线程里通过:

    while True:
        event_id , parameter = event_queue.get()
        if event_id == MSG_BTN_CLICK:
        	# 代码逻辑
    

    如果界面要更新界面数据的话,就发送个 MSG_UPDATE_LIST 之类的消息附带上窗口对象,然后主线程丽获取数据后发送个 qt 信号过去更新数据。

    不知道这个想法可不可取?谢谢指教!

    24 条回复    2018-11-11 08:19:00 +08:00
    0312birdzhang
        1
    0312birdzhang  
       2018-11-08 09:03:13 +08:00 via iPhone
    emmmm,信号和槽
    ragnaroks
        2
    ragnaroks  
       2018-11-08 09:11:13 +08:00
    在 UI 线程(往往是主线程,但不能相等)给个成员变量并监听变动,new 子线程并给予其对本线程(上文的 UI 线程)的引用,你的逻辑完成后修改 UI 线程的变量,主线程处理后续逻辑
    baixiangcpp
        3
    baixiangcpp  
       2018-11-08 09:19:12 +08:00
    sc3263
        4
    sc3263  
       2018-11-08 10:12:18 +08:00
    Qt 对界面相关的的操作只能在主线程中完成。你可以试一下这个做法: https://stackoverflow.com/questions/11033971/qt-thread-with-movetothread
    hakono
        5
    hakono  
    OP
       2018-11-08 10:21:03 +08:00
    @baixiangcpp 感谢回复。看来官方的确还是建议只在主线程里跑 UI 啊。
    不过有点搞不懂,文档里明确说了 widgets 这些不能在子线程里跑起来
    ```
    All widgets and several related classes, for example QPixmap, don't work in secondary threads
    ```
    但我在子线程里创建窗口显示按钮都很正常没问题...
    ChenFanlin
        6
    ChenFanlin  
       2018-11-08 10:24:42 +08:00
    看到上面说只有 UI 线程才能操作界面,那和 Android 差不多.
    通常都是在新线程处理数据,然后处理完了回到 UI 线程处理界面.
    Chenamy2017
        7
    Chenamy2017  
       2018-11-08 10:25:09 +08:00
    子线程不能跑 UI。既然是处理逻辑,子线程就可以啊,搞不清楚你为什么要反着来。
    pkoukk
        8
    pkoukk  
       2018-11-08 10:33:55 +08:00
    据我所知,一般都不允许子线程处理 UI,
    逻辑很简单,多个子线程处理 UI 的时候由于次序问题很容易导致界面错乱,或者使用了被其它线程释放掉的资源导致崩溃
    hakono
        9
    hakono  
    OP
       2018-11-08 10:46:57 +08:00
    @Chenamy2017 主要是想要彻底把界面和逻辑分开。因为感觉界面做主线程,处理逻辑开个子线程的话,界面代码和逻辑代码实际上依旧是混合在一起的。

    而反着来的话,界面类里就只有界面代码的,界面有操作的话,就按钮点击后就发送个类似于 MSG_BTN_CLICKED 消息到队列里。这样主线程就能捕获到这个消息,处理了逻辑了,这样界面和处理逻辑就彻底分开了。
    当然,因为我从没做过界面开发,所以可能想歪了。所以想来征求下大家的意见


    然后现在我有个更疑惑的问题,看了大家都在说子线程不能操作 UI,,就是 pyqt 似乎真的能子线程创建 UI 没问题?
    随便写了个最简单的代码例子,似乎跑起来没问题?
    https://gist.github.com/ShinonomeHana/be8ec0bf77da9503fd2076837d2b8522
    Rizio
        10
    Rizio  
       2018-11-08 13:27:01 +08:00
    Android 子线程是可以操作 ui 的,但是要在创建 Activity 的时候用非常快的速度去修改。
    原理就是做 ui 操作时会检查当前线程是否和 Activity 的创建线程(也就是主线程)是否一致,不一致就抛异常。
    不知道其他框架是不是也是这么干的。
    sc3263
        11
    sc3263  
       2018-11-08 13:58:28 +08:00
    @hakono Qt 在 windows 下允许用非主线程创建 QApplication 对象并执行事件循环。在 mac 下这样做会出错。
    dychenyi
        12
    dychenyi  
       2018-11-08 14:13:07 +08:00
    @Rizio
    估计子线程后面没有对 UI 的操作了所以没报错,你在线程 start 之后立马去创个按妞试试看,估计就报错了吧。
    cosven
        13
    cosven  
       2018-11-08 14:13:32 +08:00
    > 一般创建界面都是在主线程里创建,遇到耗时的任务时开个 QThread 子线程来处理不是吗。
    嗯,用线程或者进程

    > 随便写了个最简单的代码例子,似乎跑起来没问题?
    有问题的,比如 macOS 下,这样的程序会直接崩溃(我在 gist 下写了更多详情)

    > 我想要达到分离界面和逻辑的目的
    LZ 有没有想过这么几个问题
    1. 界面和逻辑分离有哪些好处呢?
    2. 哪些算界面部分,哪些算逻辑部分?
    3. 界面操作(改变按钮颜色、调整组件宽度、组件动画)这些算逻辑还是算界面?

    --------------------------------

    关于界面和逻辑分离的观点,一个 GUI 程序,大部分逻辑就是两种情况:
    1. 获取数据 -> 刷新界面
    2. 用户操作界面 -> 修改数据
    **大部分**情况,**界面和逻辑是密不可分的,分离界面和逻辑是个错误的决定**,分离只会让你的代码变得复杂。

    还有一部分场景:Qt 提供了一种 Model/View/Delegator 的编程模式,它解决的问题是复杂业务场景下的界面逻辑分离。
    hakono
        14
    hakono  
    OP
       2018-11-08 14:19:14 +08:00
    @sc3263 原来如此,当时我按着这个思路下试了下就跑起来了还以为这个思路是没问题的,哈哈。


    @dychenyi 不会报错,只要所有对 UI 的创建都是在那个子线程里进行的话就不会报错。
    hakono
        15
    hakono  
    OP
       2018-11-08 14:24:28 +08:00
    @cosven 多谢指点,没考虑到跨平台的问题,哈哈。 的确在 macOS 下会报错。
    经你一说的确想通了,对界面的变更操作的确不好区分,很多界面的操作逻辑和 UI 是密不可分的,如果界面要做一些变更就给主线程发送个 MSG 的话,的确反倒会造成主线程那边代码变得相当复杂
    q397064399
        16
    q397064399  
       2018-11-08 14:26:55 +08:00
    @cosven #13 分离界面的逻辑 前提是 界面操作要提供一堆声明式的接口 来满足逻辑的需要,逻辑需要做的是描述我想要的界面是什么样的,而不是关心界面是如何被操作的。
    arzterk
        17
    arzterk  
       2018-11-08 16:15:08 +08:00
    做 UI 的大致都只能主线程刷新界面,子线程处理完了丢个消息刷新界面,原因是许多 OS 的图形子系统底层都有锁,防止出现图形绘制不正确之类的

    PS.我之前做 MFC,子线程是不能直接调用 GDI API 的,强行 ShowDialog 会造成 GDI contex 错误
    SilentHill
        18
    SilentHill  
       2018-11-08 17:33:29 +08:00
    基本 90%的 ui 框架都不允许非 ui 线程操作界面。

    我觉得可以做一个 model,映射界面数据,然后 ui 和后台线程对该 model 进行更新,ui 在每次事件循环中根据 model 来更新界面。。好像就是 mvc 的思路,好久没用 qt 了。。都忘的差不多了。
    shoujiaxin
        19
    shoujiaxin  
       2018-11-08 20:25:21 +08:00 via iPhone
    既然都 Qt 了为什么不用信号和槽呢?这俩本来就可以跨线程的呀。把自己的类 moveToThread,就运行在子线程了,数据用信号来传
    innoink
        20
    innoink  
       2018-11-08 21:29:19 +08:00 via Android
    彻底分开用 qtquick,qml 写界面
    ysc3839
        21
    ysc3839  
       2018-11-09 01:20:31 +08:00 via Android
    Windows 也许可以,但不建议这么做。macOS 不行。
    印象中 macOS 非主线程或者 fork 出来的子进程不能使用 UI 相关的功能。
    zjddp
        22
    zjddp  
       2018-11-09 09:47:37 +08:00
    是 V 和 M 双向绑定的意思吗?
    lyc8801
        23
    lyc8801  
       2018-11-09 10:55:58 +08:00
    我都是在主线程创建 UI,然后子线程处理逻辑,两者通过信号槽来传递消息...话说 QT 的信号槽真的好用
    largecat
        24
    largecat  
       2018-11-11 08:19:00 +08:00 via Android
    主线程跑 UI,子线程跑任务,通过信号槽联系,
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3414 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 04:55 · PVG 12:55 · LAX 20:55 · JFK 23:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.