工作线程
主线程通常被用于运行主循环,而主循环负责的都是 UI 相关的工作,所以也可以说主线程是 UI 线程。为了不影响 UI 线程的工作效率,我们会需要创建额外的线程来负责各种各样的工作,而这些线程就是工作线程。
在主循环的章节中,我们已经了解到主循环执行频率影响界面的流畅度,它的每一次循环都会按顺序执行处理定时器、处理事件队列、更新组件、渲染组件等任务,其中最容易影响到主循环的执行频率的任务是处理事件队列,因为大部分的事件处理器都是应用程序主动绑定的,对于缺乏性能优化意识的新手而言,可能会在事件处理器中直接进行一些耗时较高的操作,从而导致界面在操作结束前一直处于未响应状态。
解决这一问题的常见做法是将操作移动到另一个线程上执行,我们可以用 LCUI 提供的工作线程池来实现:
void DoSomeThing(void *arg1, void *arg2) { printf("key: %s\n", arg1); printf("value: %s\n", arg2); } void OnButtonClick() { LCUI_TaskRec task = { 0 }; LCUI_Init(); task.arg[0] = strdup("color"); task.arg[1] = strdup("red"); task.destroy_arg[0] = free; task.destroy_arg[1] = free; task.func = DoSomeThing; LCUI_PostAsyncTask(&task); }
LCUITaskRec
类型的 task 变量描述了任务的执行函数及其参数,arg
成员变量记录了参数列表,destroy_arg
则是这些参数的销毁函数,这里我们用了 strdup()
分配了新的内存存储字符串,并指定 free()
作为参数的销毁函数。准备完任务后,调用 LCUI_PostAsyncTask()
函数将任务添加到工作线程的任务队列中等待执行。
线程安全问题
UI 资源是全局共享的,任意线程都能访问和修改它,当有多个线程在操作 UI 资源的时候,任意一个线程对 UI 的改动都有可能影响其它线程操作结果,轻则界面内容异常,重则导致应用程序崩溃,因此,UI 资源不是线程安全的。
鉴于多线程操作 UI 的需求量和性能上的考虑,LCUI 未采用互斥锁之类的机制来解决线程安全问题,我们在开发的时候应尽量在 UI 线程上集中进行 UI 操作,以此避免线程安全问题。
与 UI 线程通信
当我们需要将工作线程的处理结果更新到 UI 上的时候,可以用 LCUI_PostTask()
函数将 UI 相关操作移动到 UI 线程上执行,它的用法与 LCUI_PostAsyncTask()
相同,示例代码如下:
void UpdateText(void *arg1, void *arg2) { char *text = arg2; LCUI_Widget textview = arg1; TextView_SetText(textview, text); } ... LCUI_Widget textview; char *text = strdup("Task has been completed!"); ... LCUI_AppTaskRec task = { 0 }; task.func = UpdateText; task.arg[0] = textview; task.arg[1] = text; task.destroy_arg[0] = NULL; task.destroy_arg[1] = free; LCUI_PostTask(&task); // ...
如果任务的参数不需要销毁,则可以用 LCUI_PostSimpleTask()
函数式宏代替 LCUI_PostTask()
,以节省 LCUI_AppTask
对象的构造代码。
自定义工作线程池
LCUI 的工作线程池中默认创建了 4 个工作线程,为了让这些工作线程都有任务执行,LCUI_PostAsyncTask()
函数会在每次投递完任务后将目标切换到下一个工作线程,如果这种简单的任务分配策略不符合你的需求,你也可以基于 src/worker.c 提供的函数创建自己的工作线程池:
// 创建一个带有任务队列的工作线程,然后运行它 LCUI_Worker worker = LCUIWorker_New(); LCUIWorker_RunAsync(worker); ... // 给工作线程投递任务 LCUI_TaskRec task = { 0 }; task.arg[0] = strdup("color"); task.arg[1] = strdup("red"); task.destroy_arg[0] = free; task.destroy_arg[1] = free; task.func = DoSomeThing; LCUIWorker_PostTask(worker, task); ... // 销毁工作线程 LCUIWorker_Destroy(worker);