在SOUI4中工作线程如果与UI线程交互

很多时候程序的耗时过程需要在工作线程执行,执行过程中可能需要通过UI线程来展示运行状态及结果,这就涉及到工作线程与UI线程交互的问题。

SOUI的UI框架本身不是线程安全的,如果在工作线程直接操作UI元素,运气好就是数据不正常,运气坏一点就是程序崩溃。因此正确的在工作线程操作UI元素是一个非常重要的问题。

早期SOUI提供了一个NotifyCenter对象,用户可以在工作线程包装一个事件发给NotifyCenter,NotifyCenter会在UI线程定时检查事件队列,再把事件传递到UI线程。这种方式使用起来相对复杂。

从4.0开始,SOUI的MsgLoop实现了一个PostTask方法,用户在工作线程拿到主线程的IMessageLoop即可使用IMessageLoop::PostTask方法将一个异步任务交给主线程去执行。

如果没有模式窗口,一个程序的主线程只有一个IMessageLoop对象,用户拿到这个对象后,就可以保证PostTask的任务会被主线程执行,但是如果程序运行过程中有弹出MessageBox等模式窗口,则程序中可能同时存在多个IMessageLoop对象,因此通过IApplication::GetMsgLoop方法拿到主线程的IMessageLoop,并PostTask到这个对象,有可能不能被即时执行。考虑到这个可能的副作用,我也一直没有向大家推荐这个方法,只是我自己偷偷的使用(因为只有我知道使用过程中可能的坑)。

最近通过对IMessageLoop进行重新设计,引用了parent messageloop的概念,这样设计以后,一个IMessageLoop在启动前会获取当前正在运行的IMessageLoop对象,称之为parent msgloop, 在新的msgloop运行的时候,会自动执行parent msgloop中的async task list,从而保证了通过IApplication::GetMsgLoop提交的异步任务,无论当前是哪个msgloop在运行都会被即时执行。

因此在4.4中工作线程要切换到UI线程,最简单的方法,就是获取主线程的msgloop并通过PostTask方法将一个异步任务交给主线程去执行。

例如sliveplayer里的SVodPlayer 用法:

 1 class SVodPlayer : public ITransVodListener {
2 public:
3 Value m_cbHandler;
4 Value m_onError;
5 Value m_onDuration;
6 Value m_onPlayPosition;
7 Value m_onStateChanged;
8
9 protected:
10 IMessageLoop* GetMsgLoop() {
11 return m_presenter->GetHostWnd()->GetMsgLoop();
12 }
13 STDMETHOD_(void, onError)(THIS_ LPCSTR url, transvod::ErrorCode errCode, int statusCode) override {
14 SStringA strUrl(url);
15 STaskHelper::post(GetMsgLoop(), this, &SVodPlayer::_onError, strUrl, errCode, statusCode);
16 }
17
18 STDMETHOD_(void, _onError)(THIS_ LPCSTR url, transvod::ErrorCode errCode, int statusCode) {
19 if (m_onError.IsFunction()) {
20 Context* ctx = m_onError.context();
21 Value args[2] = {NewValue(*ctx,(int)errCode),NewValue(*ctx,statusCode)};
22 ctx->Call(m_cbHandler, m_onError, 2, args);
23 }
24 }
25
26 STDMETHOD_(void, onTotalTime)(THIS_ LPCSTR url, uint32_t totalTime) override {
27 SStringA strUrl(url);
28 STaskHelper::post(GetMsgLoop(), this, &SVodPlayer::_onTotalTime, strUrl, totalTime);
29 }
30 STDMETHOD_(void, _onTotalTime)(THIS_ SStringA& url, uint32_t totalTime) {
31 if (m_onDuration.IsFunction()) {
32 Context* ctx = m_onDuration.context();
33 Value args = NewValue(*ctx, totalTime);
34 ctx->Call(m_cbHandler, m_onDuration, 1, &args);
35 }
36 }
37
38 STDMETHOD_(void, onPlayedTimeChanged)(THIS_ LPCSTR url, uint32_t playedTime) override {
39 SStringA strUrl(url);
40 STaskHelper::post(GetMsgLoop(), this, &SVodPlayer::_onPlayedTimeChanged, strUrl, playedTime);
41 }
42 STDMETHOD_(void, _onPlayedTimeChanged)(THIS_ LPCSTR url, uint32_t playedTime) {
43 if (m_onPlayPosition.IsFunction()) {
44 Context* ctx = m_onPlayPosition.context();
45 Value args[1] = { NewValue(*ctx,playedTime) };
46 ctx->Call(m_cbHandler, m_onPlayPosition, 1, args);
47 }
48 }
49
50 STDMETHOD_(void, onStateChanged)(THIS_ LPCSTR url, transvod::PlayerState state, transvod::ErrorReason reason) override {
51 SStringA strUrl(url);
52 STaskHelper::post(GetMsgLoop(), this, &SVodPlayer::_onStateChanged, strUrl, state, reason);
53 }
54 STDMETHOD_(void, _onStateChanged)(THIS_ LPCSTR url, transvod::PlayerState state, transvod::ErrorReason reason) {
55 if (m_onStateChanged.IsFunction()) {
56 Context* ctx = m_onStateChanged.context();
57 Value args[2] = { NewValue(*ctx,(int)state),NewValue(*ctx,(int)reason) };
58 ctx->Call(m_cbHandler, m_onStateChanged, 2, args);
59 }
60 }
61 };

播放器的回调函数都是从播放器的线程过来的,要更新UI的状态,我需要将它切换到UI线程来执行,通过GetMsgLoop(),我可以获取到UI线程的IMessageLoop对象,然后通过STaskHelper::post这个方法,可以将回调函数及各种参数打包到一个IRunnable里, 然后就会在UI线程执行这个Runnable。可以看出来使用起来还是非常简单的。

例如SVodPlayer::onStateChanged是播放器线程的回调函数,SVodPlayer::_onStateChanged则是UI线程执行这个回调的地方。参数类型都基本一样。

使用这种方法实现异步任务需要注意2个问题:

1 需要注意的是参数的生命周期。

比如SVodPlayer::onStateChanged的第一个参数是一个LPCSTR类型,如果在STaskHelper::post的时候,直接把这个url参数打包到IRunnable中,这个参数在SVodPlayer::_onStateChanged执行的时候已经失效了,因此在UI线程再访问这个参数的内存空间则会出错。

使用STaskHelper::post打包参数的时候,是使用模板技术,将所用到的参数复制一份。显然复制裸指针是不行的,因此我使用了一个SStringA对象,将这个url复制一份到strUrl中,STaskHelper::post再把这个strUlr复制一份到打包的IRunnable中,从而保证这个url在执行的时候是有效的。

2 另一个需要注意的问题在于挂起的异步任务清理

如果一个对象析构了,而这个对象可能还有挂在MsgLoop的异步任务没有执行完成。IMessageLoop提供了一个方法:IMessageLoop::RemoveTasksForObject(void *pObj), pObj是执行异步任务的对象。当前对象析构前,应该调用这个方法把挂起的异步任务清空,才能安全的释放对象。

在SOUI4中工作线程如果与UI线程交互的更多相关文章

  1. Android中后台线程如何与UI线程交互

    我想关于这个话题已经有很多前辈讨论过了.今天算是一次学习总结吧. 在android的设计思想中,为了确保用户顺滑的操作体验.一些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务.因此我们必须 ...

  2. Android UI线程和非UI线程

    Android UI线程和非UI线程 UI线程及Android的单线程模型原则 当应用启动,系统会创建一个主线程(main thread). 这个主线程负责向UI组件分发事件(包括绘制事件),也是在这 ...

  3. android脚步---如何看log之程序停止运行,和UI线程和非UI线程之间切换

    经常运行eclipse时,烧到手机出现,“停止运行”,这时候得通过logcat查log了.一般这种情况属于FATAL EXCEPTION,所以检索FATAL 或者 EXCEPTION,然后往下看几行 ...

  4. 关于 SWT 的UI线程和非UI线程

    要理解UI线程,先要了解一下“消息循环”这个概念.链接是百度百科上的条目,简单地说,操作系统把用户界面上的每个操作都转化成为对应的消息,加入消息队列.然后把消息转发给对应的应用程序(一般来说,就是活动 ...

  5. Qt5中运行后台网络读取线程与主UI线程互交

    项目中有一个需求就是,因为需要请求服务端数据,因为网络的读取会阻塞,所以该过程不能放在Qt中的UI主线程当中,需要用一个后台线程来读取数据,数据准备完毕后 在通过Qt5中的信号槽机制来跨线程的传递数据 ...

  6. Qt中运行后台线程不阻塞UI线程的方案

    有一个想法,一个客户端,有GUI界面的同时也要向网络服务器发送本地采集的数据,通过网络发送数据的接口是同步阻塞的,需要等待服务器响应数据. 如果不采用后台线程的方案,用主UI线程关联一个定时器QTim ...

  7. Android线程---UI线程和非UI线程之间通信

        近期自学到了线程这一块,用了一上午的时间终于搞出来了主.子线程间的相互通信.当主线程sendMessage后,子线程便会调用handleMessage来获取你所发送的Message.我的主线程 ...

  8. 解决DoubanFM第三方客户端UI线程与工作线程交互问题

    最新文章:Virson's Blog 首先要感谢yk000123的慷慨开源,开源地址见:http://doubanfm.codeplex.com/ 最近正好在学习WPF,然后在Codeplex上找到了 ...

  9. 如何在子线程中使用Toast和更新UI

    因为没一个Looper处理消息循环,所以子线程中无法使用Toast 方法: Looper.prepare(); Toast.makeText(getActivity(),"刷到底啦" ...

  10. Android中UI线程与后台线程交互设计的6种方法

    在android的设计思想中,为了确保用户顺滑的操作体验.一些耗时的任务不能够在UI线程中运行,像访问网络就属于这类任务.因此我们必须要重新开启 一个后台线程运行这些任务.然而,往往这些任务最终又会直 ...

随机推荐

  1. JAVA反序列化学习-前置知识(基于ysoserial)

    本人在学习Java反序列化中,发现网上大多都是自己分析一些逻辑内容,导致可能每一个版本的payload都不相同,对于学习时并不友好,所以我在学习了cc链之后,准备总结一下cc链中的内容,并以ysose ...

  2. SpringBoot实现轻量级动态定时任务管控及组件化

    关于动态定时任务 关于在SpringBoot中使用定时任务,大部分都是直接使用SpringBoot的@Scheduled注解,如下: @Component public class TestTask ...

  3. 升级Linux内核版本

    ```shell# 查看内核版本,jw版本ceph默认format=2, 2.x 及之前的的内核版本需手动调整format=1# 4.x之前要关闭object-map fast-diff deep-f ...

  4. Android开发重要知识点

    一.网络 1.https原理 2.tcp/ip协议 三次握手:https://www.cnblogs.com/cenglinjinran/p/8482412.html 四次挥手:https://www ...

  5. geocodeCN:一个批量将地址转为地理坐标的插件

    目录 1. 介绍 2. 使用步骤: 2.1 安装 2.2 配置 2.3 坐标匹配 2.4 生成图层 2.5 导出为CSV 3. 说明 1. 介绍 这是一个QGIS插件,主要用于批量地理编码,即将地址转 ...

  6. docker环境一个奇怪的问题,容器进程正常运行,但是docker ps -a却找不到容器,也找不到镜像

    一: 问题: docker环境一个奇怪的问题,使用容器跑的进程正常提供服务,在服务器上也能看到对应的端口正在监听,但是docker ps -a却找不到容器,也找不到镜像. 查看我使用docker容器启 ...

  7. 推荐一款轻量级且强大的 Elasticsearch GUI : elasticvue

    推荐一款轻量级且强大的 Elasticsearch GUI : elasticvue 很多同学都是用过 Elasticsearch 的 GUI 工具 Kibana ,但 Kibana 相对比较重,这篇 ...

  8. ChatGPT生成测试用例的最佳实践(二)

    这种测试用例还不够直观,能不能让其以表格的形式显示呢?笔者输入"请以表格形式展示,谢谢."提示词,ChatGPT输出的部分内容如图3-3所示. 图3-3  ChatGPT输出的部分 ...

  9. 在 VS Code 中可以免费使用 GitHub Copilot了!

    今天,有一个重大的好消息要分享给大家: 从现在开始,我们可以在 Visual Studio Code 中,免费使用强大的 GitHub Copilot 进行开发啦! 每个人都可以享受到 AI 加持下的 ...

  10. OpenLens 6.3.0 无法查案日志和进入 Pod Shell 解决方法

    原因 OpenLens 6.3.0开始移除了Pod的查看日志和进入Pod Shell按钮,无法查看日志和进入Pod操作. 解决办法 OpenLens 6.3.0开始这两个功能以插件形式提供,需下载op ...