Build Your Own HandlerThread Part 3
前面我们实现了 MessageLoop
,现在该轮到 TaskRunner
了。
在我们的设计中,TaskRunner
的定位是类似 Handler
,用来驱动/使用 MessageLoop
,将 MessageLoop
隐藏在日常使用中。
前面我们实现了 MessageLoop
,现在该轮到 TaskRunner
了。
在我们的设计中,TaskRunner
的定位是类似 Handler
,用来驱动/使用 MessageLoop
,将 MessageLoop
隐藏在日常使用中。
有了 MessagePumpDefault
之后,我们就可以开始着手实现最核心的 MessageLoop
了。
不过在此之前,我们还首先需要实现一个辅助设施:PendingTask
。
一个 PendingTask
instance 表示一个等待被执行的 task,并且这个 task 可能是一个 delayed task。
因此 PendingTask
需要能够表示时序上的顺序,这个可以利用 Instant
类型的一个时间戳,结合一个 long
类型的 sequence-number。因为有可能两个 delayed tasks 的时间戳相同,此时就必须要用 seq-num 来区分先后顺序。
另外,PendingTask
为了能够表示 task 语义,他必须可以被执行。这可以通过内部存储一个 Runnable
或者 Callable
成员做到。
我们的实现选择 Runnable
,因为
PostTaskAndReplyWithResult()
来组合两个函数;或者更一般的,使用 continuation 来 lifting restrictions1 | class PendingTask { |
我们使用了 AtomicLong
来存储当前的 sequence-number,因为我们不知道某个 PendingTask
会在哪个线程上被创建。
对于我们的 ActiveThread 来说,核心仍然是持续不断运行的 message-loop;但是在实现 message-loop 前,我们需要首先来实现它的“引擎”:MessagePump
。
大体上,message-loop 要维持运转,就要能够源源不断地从某个地方获取消息/事件,而获取消息/事件的这个行为,就是由 message-pump 来完成,正如他的名字一样。
这里可能会产生一个疑问,为什么要单独抽出一个 message-pump,而不是直接把这部分实现做到 message-loop 里呢?
原因是:一个复杂的系统可能会有多个消息/事件来源,每个来源会有专门的获取、分发的方式。
例如:
GetMessage()
select(2)
, poll(2)
;Linux 独有的 epoll
;以及 Windows 提供的真-异步I/O机制 IO Completion Port。注:GUI 系统中多使用 messages(消息)作为术语;而网络 I/O 等倾向于使用 events(事件)作为使用术语。因为此系列文章不涉及网络 I/O,因此以后均采用_消息_作为我们的使用术语。
注1:对于 Android 系统,他的 GUI 事件实质上是通过 epoll
监听关联到 device 抽象的 pipe fd 来完成的;Linux 的各类 X-Window 实现大多也是类似做法。
所以为了解耦具体的消息来源,同时让我们的实现看上去更加有逼格,我们采取分离实现 message-pump 和 message-loop。
前面说到可能有多种 pump,所以这里我们的 MessagePump
应该是一个 interface,规定了一个 message-pump 应该具有哪些基本的行为:
1 | import java.time.Instant; |
同时我们定义了一个 Delegate
interface,message-loop 需要实现这个 interface,以便让 message-pump 可以通过这个 interface 来按照自己的实现来操作 message-loop。
我们的 ActiveThread 运行的纯 Java 环境,不需要像 Android 那样能够获取设备输入消息(Frankly,你想做还做不了呢,我哪来的设备信息获取啊),有点类似 worker HandlerThread.
HandlerThread
是 Android runtime 提供的一种线程基础设施;和 Java Thread 相比,HandlerThread
最大的优点是自带消息循环(message-loop),这使得用户可以很方便地基于 HandlerThread
实现 CSP (Communicating Sequential Process) 模型,极大降低多线程编程的复杂度。
Version: r70_3538
File: base/message_loop/message_loop.{h, cc}
A MessageLoop
is used to process events for a particular thread, i.e. the core infrastructure for implementing Communicating Sequential Process (CSP) model.
There is at most one MessageLoop instance on a thread.
MessageLoop
is a farily complex class, it driveds from multiple base classes:
1 | class BASE_EXPORT MessageLoop : public MessagePump::Delegate, |
A MessageLoop has a particular type, differred by the set of asynchronous events it is capable of handling.
在 cmder 里如果不指定运行的 terminal 则会默认使用 windows cmd,而这在使用 git-bash 或者 wsl 时会经常遇到问题: