在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. git 阻止在某个分支上面提交commit

    比如在开发中不希望master分支被commit做提交,那么我们可以这样做 找到 .git/hook/文件夹 然后在里面复制一个 pre-commit出来 cd .git/hooks/ cp pre- ...

  2. meta-analysis初学--笔记摘抄

    meta-analysis的定义 Meta-analysis是指对研究的研究,可以翻译为元分析.后设分析.整合分析.荟萃分析等.最常用的翻译是荟萃分析.Meta-analysis是用统计的概念与方法, ...

  3. json-lib(ezmorph)、gson、flexJson、fastjson、jackson对比,实现java转json,json转java

    json-lib(ezmorph).gson.flexJson.fastjson.jackson对比,实现java转json,json转java 本文中所讲的所有代码都在此:json-test 目前关 ...

  4. 基于 JUnit 的全局单元测试程序

    在 Java 程序中,JUnit 是备受开发人员喜爱的单元测试工具.通常,程序员会对每个程序的每个模块写单元测试.对于小型程序来说,程序员只需要手工执行这些单元测试程序就可以,工作量并不大,但是对于中 ...

  5. java中的集合包简要分析

    1.集合包 集合包是java中最常用的包,它主要包括Collection和Map两类接口的实现. 对于Collection的实现类需要重点掌握以下几点: 1)Collection用什么数据结构实现? ...

  6. MySQL之数据排序

    在MySQL中,我们经常需要从数据库中检索数据,并根据特定的要求对数据进行排序.通常情况下,我们会根据数据中某一列的值进行排序,例如按照价格从低到高或从高到低对商品进行排序.但有时候,我们需要在数据中 ...

  7. go官方包依赖管理工具之mod

    1.1.go mod是什么 go mod 是Golang 1.11 版本引入的官方包(package)依赖管理工具,用于解决之前没有地方记录依赖包具体版本的问题,方便依赖包的管理. 之前Golang ...

  8. Reverse花指令及反混淆

    花指令及反混淆 1.花指令   花指令是反调试的一种基本的方法.其存在是干扰选手静态分析,但不会影响程序的运行.实质就是一串垃圾指令,它与程序本身的功能无关,并不影响程序本身的逻辑.在软件保护中,花指 ...

  9. windows server系统中,Pro运行深度学习工具错误

    安装深度学习包后,运行相关工具的时候报错,缺失cv2的模块. 在arcpy执行窗口,直接去引入cv2包的时候,确实发了错误. 查看了相关路径,确认cv2的包,在对应路径已经存在,也有对应的元数据信息, ...

  10. 【滑动窗口】codeforces 1290 A. Mind Control

    题意 第一行输入一个正整数 \(T(1 \leq T \leq 1000)\),表示共有 \(T\) 组测试用例.对于每一组测试用例: 第一行输入三个正整数 \(n, m, k(1 \leq m \l ...