首页 > 其他分享 >在SOUI4中工作线程如果与UI线程交互

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

时间:2022-12-21 11:36:41浏览次数:38  
标签:onStateChanged url IMessageLoop ctx UI SOUI4 线程

在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, one rror)(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是执行异步任务的对象。当前对象析构前,应该调用这个方法把挂起的异步任务清空,才能安全的释放对象。

标签:onStateChanged,url,IMessageLoop,ctx,UI,SOUI4,线程
From: https://www.cnblogs.com/setoutsoft/p/16995851.html

相关文章