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

2018-11-08 08:57:27 +08:00
 hakono

刚接触 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 信号过去更新数据。

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

8102 次点击
所在节点    Python
24 条回复
0312birdzhang
2018-11-08 09:03:13 +08:00
emmmm,信号和槽
ragnaroks
2018-11-08 09:11:13 +08:00
在 UI 线程(往往是主线程,但不能相等)给个成员变量并监听变动,new 子线程并给予其对本线程(上文的 UI 线程)的引用,你的逻辑完成后修改 UI 线程的变量,主线程处理后续逻辑
baixiangcpp
2018-11-08 09:19:12 +08:00
sc3263
2018-11-08 10:12:18 +08:00
Qt 对界面相关的的操作只能在主线程中完成。你可以试一下这个做法: https://stackoverflow.com/questions/11033971/qt-thread-with-movetothread
hakono
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
2018-11-08 10:24:42 +08:00
看到上面说只有 UI 线程才能操作界面,那和 Android 差不多.
通常都是在新线程处理数据,然后处理完了回到 UI 线程处理界面.
Chenamy2017
2018-11-08 10:25:09 +08:00
子线程不能跑 UI。既然是处理逻辑,子线程就可以啊,搞不清楚你为什么要反着来。
pkoukk
2018-11-08 10:33:55 +08:00
据我所知,一般都不允许子线程处理 UI,
逻辑很简单,多个子线程处理 UI 的时候由于次序问题很容易导致界面错乱,或者使用了被其它线程释放掉的资源导致崩溃
hakono
2018-11-08 10:46:57 +08:00
@Chenamy2017 主要是想要彻底把界面和逻辑分开。因为感觉界面做主线程,处理逻辑开个子线程的话,界面代码和逻辑代码实际上依旧是混合在一起的。

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


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

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

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

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

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

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


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

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

我觉得可以做一个 model,映射界面数据,然后 ui 和后台线程对该 model 进行更新,ui 在每次事件循环中根据 model 来更新界面。。好像就是 mvc 的思路,好久没用 qt 了。。都忘的差不多了。
shoujiaxin
2018-11-08 20:25:21 +08:00
既然都 Qt 了为什么不用信号和槽呢?这俩本来就可以跨线程的呀。把自己的类 moveToThread,就运行在子线程了,数据用信号来传
innoink
2018-11-08 21:29:19 +08:00
彻底分开用 qtquick,qml 写界面

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/505607

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX