在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. 返璞归真!使用 alpinejs 开发交互式 web 应用,抛弃 node_modules 和 webpack 吧!

    前言 最近一直在使用 DjangoStarter 开发各种小项目,之前我是比较喜欢前后端分离的,后端用 Ninja API,前端 nextjs,开发起来也挺舒服的,交互体验也比较好. 不过我在网上冲浪 ...

  2. core-js版本过低,需要更新但是更新失败的原因

    ore-js@2.6.12: core-js@.3 is no longer maintained and not recommended for usage due to the number of ...

  3. ArrayList removeRange方法分析

    <ArrayList原码分析>一文中提到了"为什么removeRange(int fromIndex,int toIndex)是protected的?" 先给出remo ...

  4. Nodejs调试之Chrome Devtools

    转载: https://mp.weixin.qq.com/s/tqGWizPUFnuVWRcXcxyv2g 俗话说:"工欲善其事,必先利其器",调试是每一个开发人员都要遇到的问题, ...

  5. Kafka之使用

    windows下的管理工具: kafka-tool:  https://www.kafkatool.com/download.html [Windows] 常用命令: # 查看topic 列表 ./b ...

  6. 秒懂Redis

    一.redis简介 Redis 是C语言开发的一个开源高性能键值对的内存数据库,可以用来做数据库.缓存.消息中间件等场景,是一种NoSQL(not-only sql,非关系型数据库)的数据库 二.Re ...

  7. python开发包之远程隧道链接sshtunnel

    缘起: 公司很多的数据库的链接都是本地连接或者指定ip地址可以访问, 如果你没有该ip权限, 但是你可以登录该数据库所在的服务器, 这个时候就可以使用ssh链接上这个服务器,以此为跳板进行数据库的链接 ...

  8. fiddler:The system proxy was changed.Click to reenable capturing

    前情 最近在开发一个老旧项目,由于本地环境已难跑起,于是想通过代理线上代码进行功能开发. 坑位 启动fiddler后,fiddler菜单栏会警告,大概意思是代理被更改了,点击重启fillder代理,但 ...

  9. 埃尼阿克ENIAC与计算机发展,及信息技术发展史

    一.埃尼阿克ENIAC 第二次世界大战期间,国军方为了研发新型的大炮和导弹,设立了"弹道研究实验室".实验室为了计算炮弹弹道,用了200多人加班加点进行计算,速度依感无法达到军方要 ...

  10. ng-alain: Title Service

    文档地址:https://ng-alain.com/theme/title/zh 源码地址: https://github.com/ng-alain/delon/blob/master/package ...