延时任务的处理通常包括如下几个要素:
a). 任务;
b). 延时任务队列;
c). 计时器,循环检测延时任务队列,若有任务到时则取出,然后执行;
下面分析下GCD中关于libdispatch的延时任务处理方式。
下面这部分代码是根据自身需求,封装的一个接口,基本能满足异步延时任务的处理:
void _dispatch_delayed(dispatch_time_t when,
dispatch_queue_t queue, WorkItem* item) {
uint64_t delta;
dispatch_source_t ds = NULL;
if (when == DISPATCH_TIME_FOREVER) {
return;
}
//封装source
ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (!ds) {
return;
}
//创建延时ContextForTimer
ContextForTimer* Context = new ContextForTimer(item, ds);
if (!Context) {
dispatch_release(ds);
return;
}
//设置时间参数
dispatch_set_context(ds, reinterpret_cast(Context));
//封装并派遣“为ds设置/取消计时器“任务
dispatch_source_set_event_handler_f(ds, _dispatch_after_timer_callback);
dispatch_source_set_cancel_handler_f(ds, _dispatch_after_timer_cancel);
//派遣ds任务
dispatch_source_set_timer(ds, when, 0, 0);
//若ds插入队列,对理解并没有唤醒,resume将起到队列唤醒的左右
dispatch_resume(ds);
}
延时任务的几个基本要素:任务,时间,队列,都有了,
先封装一个_dispatch_source_t;
用途: 存储延时任务相关的一个队列,这个队列用于调度延时任务相关的一些操作,比如为任务设置时间参数;
ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t q)
{
dispatch_source_t ds = NULL;
static char source_label[sizeof(ds->dq_label)] = "source";
// input validation
if (type == NULL || (mask & ~type->mask)) {
goto out_bad;
}
ds = calloc(1ul, sizeof(struct dispatch_source_s));
if (slowpath(!ds)) {
goto out_bad;
}
// Initialize as a queue first, then override some settings below.
_dispatch_queue_init((dispatch_queue_t)ds);
memcpy(ds->dq_label, source_label, sizeof(source_label));
// Dispatch Object
ds->do_vtable = &_dispatch_source_kevent_vtable;
ds->do_ref_cnt++; // the reference the manger queue holds
ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;
// do_targetq will be retained below, past point of no-return
ds->do_targetq = q;
if (slowpath(!type->init(ds, type, handle, mask, q))) {
goto out_bad;
}
dispatch_assert(!(ds->ds_is_level && ds->ds_is_adder));
#if DISPATCH_DEBUG
dispatch_debug(ds, __FUNCTION__);
#endif
_dispatch_retain(ds->do_targetq);
return ds;
out_bad:
free(ds);
return NULL;
}
source结构
struct dispatch_source_s {
DISPATCH_STRUCT_HEADER(dispatch_source_s, dispatch_source_vtable_s);
DISPATCH_QUEUE_HEADER;
// Instruments always copies DISPATCH_QUEUE_MIN_LABEL_SIZE, which is 64,
// so the remainder of the structure must be big enough
union {
char _ds_pad[DISPATCH_QUEUE_MIN_LABEL_SIZE];
struct {
char dq_label[8];
dispatch_kevent_t ds_dkev;
dispatch_function_t ds_handler_func;
void *ds_handler_ctxt;
void *ds_cancel_handler;
unsigned int ds_is_level:1,
ds_is_adder:1,
ds_is_installed:1,
ds_needs_rearm:1,
ds_is_armed:1,
ds_is_legacy:1,
ds_cancel_is_block:1,
ds_handler_is_block:1;
unsigned int ds_atomic_flags;
unsigned long ds_data;
unsigned long ds_pending_data;
unsigned long ds_pending_data_mask;
TAILQ_ENTRY(dispatch_source_s) ds_list;
unsigned long ds_ident_hack;
struct dispatch_timer_source_s ds_timer;
};
};
};
初始化:
ds = calloc(1ul, sizeof(struct dispatch_source_s));
_dispatch_queue_init((dispatch_queue_t)ds);
inline void _dispatch_queue_init(dispatch_queue_t dq)
{
dq->do_vtable = &_dispatch_queue_vtable;
dq->do_next = DISPATCH_OBJECT_LISTLESS;
dq->do_ref_cnt = 1;
dq->do_xref_cnt = 1;
dq->do_targetq = _dispatch_get_root_queue(0, true);
dq->dq_running = 0;
dq->dq_width = 1;
dq->dq_serialnum = dispatch_atomic_inc(&_dispatch_queue_serial_numbers) - 1;
}
这里初始化调用的是_dispatch_queue_init((dispatch_queue_t)ds); 也就是将ds左右一个dq对象来初始化,其实无所谓,这只是初始化,只要保证所有变量都初始化就好,谁叫人家是透明联合体呢。
// Dispatch Object
ds->do_vtable = &_dispatch_source_kevent_vtable;
ds->do_ref_cnt++; // the reference the manger queue holds
ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;
// do_targetq will be retained below, past point of no-return
ds->do_targetq = q;
#define DISPATCH_OBJECT_SUSPEND_INTERVAL 2u
ds->do_targetq = q;
把关键信息解释一下:
ds->do_vtable = &_dispatch_source_kevent_vtable; 设置针对source的source_kevent_vtable
ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL; 表明这是个suspend;
ds->do_targetq = q; 这个ds的目标队列就是要传递过来的队列;
(2). 其他初始化
if (slowpath(!type->init(ds, type, handle, mask, q))) {
goto out_bad;
}
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
#define DISPATCH_SOURCE_TYPE_TIMER (&_dispatch_source_type_timer)
const struct dispatch_source_type_s _dispatch_source_type_timer = {
.opaque = (void *)&_dispatch_source_type_timer_ke,
.mask = DISPATCH_TIMER_INTERVAL|DISPATCH_TIMER_ONESHOT|DISPATCH_TIMER_ABSOLUTE|DISPATCH_TIMER_WALL_CLOCK,
.init = dispatch_source_type_timer_init,
};
static const struct kevent _dispatch_source_type_timer_ke = {
.filter = DISPATCH_EVFILT_TIMER,
};
因此调用的init就是调用了dispatch_source_type_timer_init
static bool
dispatch_source_type_timer_init(dispatch_source_t ds, dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t q)
{
if (!dispatch_source_type_kevent_init(ds, type, handle, mask, q)) {
return false;
}
ds->ds_needs_rearm = true;
ds->ds_timer.flags = mask;
return true;
}
其中ds就是刚才创建的ds,
type就是DISPATCH_SOURCE_TYPE_TIMER
handle是NULL;
q是最初的dq;
接下来进入到了:
static bool
dispatch_source_type_kevent_init(dispatch_source_t ds, dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t q)
{
const struct kevent *proto_kev = type->opaque;
dispatch_kevent_t dk = NULL;
switch (proto_kev->filter) {
case EVFILT_SIGNAL:
if (handle >= NSIG) {
return false;
}
break;
case EVFILT_FS:
case DISPATCH_EVFILT_CUSTOM_ADD:
case DISPATCH_EVFILT_CUSTOM_OR:
case DISPATCH_EVFILT_TIMER:
if (handle) {
return false;
}
break;
default:
break;
}
dk = calloc(1ul, sizeof(struct dispatch_kevent_s));
if (slowpath(!dk)) {
return false;
}
dk->dk_kevent = *proto_kev;
dk->dk_kevent.ident = handle;
dk->dk_kevent.flags |= EV_ADD|EV_ENABLE;
dk->dk_kevent.fflags |= (uint32_t)mask;
dk->dk_kevent.udata = dk;
TAILQ_INIT(&dk->dk_sources);
// Dispatch Source
ds->ds_ident_hack = dk->dk_kevent.ident;
ds->ds_dkev = dk;
ds->ds_pending_data_mask = dk->dk_kevent.fflags;
if ((EV_DISPATCH|EV_ONESHOT) & proto_kev->flags) {
ds->ds_is_level = true;
ds->ds_needs_rearm = true;
} else if (!(EV_CLEAR & proto_kev->flags)) {
// we cheat and use EV_CLEAR to mean a "flag thingy"
ds->ds_is_adder = true;
}
return true;
}
进入到这里来,也许就开始一头雾水了,为什么跟kevent扯上关系了;
但是我们需要知道,在这个init里面为ds设置了一个dk_kevent;
ds->ds_ident_hack = dk->dk_kevent.ident;
ds->ds_dkev = dk;
ds->ds_pending_data_mask = dk->dk_kevent.fflags;
if ((EV_DISPATCH|EV_ONESHOT) & proto_kev->flags) {
ds->ds_is_level = true;
ds->ds_needs_rearm = true;
} else if (!(EV_CLEAR & proto_kev->flags)) {
// we cheat and use EV_CLEAR to mean a "flag thingy"
ds->ds_is_adder = true;
}
在文章的初始,我讲述了延时任务执行的几个关键因素,下面我给出GCD的处理原理
GCD针对异步延时任务,至少有两个队列,一个事件队列,一个任务队列;
a). 事件队列就是图中指示的_dispatch_mgr_q;
b). 延时任务存放在_dispatch_mgr_q的一个指针指向的队列中,延时任务的插入也是异步的;
c). 事件检测线程在不停地轮询中,做了两件事
这就是延时任务执行的一个大概原理;
static void
_dispatch_callback(void *ctxt) {
ContextForTimer* Context = reinterpret_cast(ctxt);
if (Context) {
Context->run();
}
}
用途:ContextForTimer作为_dispatch_callback的一个参数;_dispatch_callback会作为“延时任务”结构中的一个函数指针;当延时任务被调度时,执行的就是_dispatch_callback;ContextForTimer则会作为“延时任务”结构的一个上下文ctxt;
void
dispatch_set_context(dispatch_object_t dou, void *context)
{
struct dispatch_object_s *obj = DO_CAST(dou);
if (obj->do_ref_cnt != DISPATCH_OBJECT_GLOBAL_REFCNT) {
obj->do_ctxt = context;
}
}
dispatch_source_set_event_handler_f(ds, _dispatch_callback);
dispatch_source_set_cancel_handler_f(ds, _dispatch_cancel);
而dispatch_source_set_event_handler_f,将这两个指针封装成了两个root队列的任务;
dispatch_source_set_event_handler_f(ds, _dispatch_callback);
_dispatch_callback只是作为一个handler,设置这个Handler的是 _dispatch_source_set_event_handler_f
void
dispatch_source_set_event_handler_f(dispatch_source_t ds,
dispatch_function_t handler)
{
dispatch_assert(!ds->ds_is_legacy);
dispatch_barrier_async_f((dispatch_queue_t)ds,
handler, _dispatch_source_set_event_handler_f);
}
看看 _dispatch_source_set_event_handler_f 做了啥子事情:
static void
_dispatch_source_set_event_handler_f(void *context)
{
dispatch_source_t ds = (dispatch_source_t)_dispatch_queue_get_current();
dispatch_assert(ds->do_vtable == &_dispatch_source_kevent_vtable);
#ifdef __BLOCKS__
if (ds->ds_handler_is_block && ds->ds_handler_ctxt) {
Block_release(ds->ds_handler_ctxt);
}
#endif
ds->ds_handler_func = context;
ds->ds_handler_ctxt = ds->do_ctxt;
ds->ds_handler_is_block = false;
}
原来:
void
dispatch_barrier_async_f(dispatch_queue_t dq, void *context, dispatch_function_t func)
{
dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_cacheonly());
if (!dc) {
return _dispatch_barrier_async_f_slow(dq, context, func);
}
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
dc->dc_func = func;
dc->dc_ctxt = context;
_dispatch_queue_push(dq, dc);
}
DISPATCH_NOINLINE
static void
_dispatch_barrier_async_f_slow(dispatch_queue_t dq, void *context, dispatch_function_t func)
{
dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_from_heap());
dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
dc->dc_func = func;
dc->dc_ctxt = context;
_dispatch_queue_push(dq, dc);
}
当程序执行到_dispatch_queue_push_list_slow
DISPATCH_NOINLINE
void
_dispatch_queue_push_list_slow(dispatch_queue_t dq, struct dispatch_object_s *obj)
{
// The queue must be retained before dq_items_head is written in order
// to ensure that the reference is still valid when _dispatch_wakeup is
// called. Otherwise, if preempted between the assignment to
// dq_items_head and _dispatch_wakeup, the blocks submitted to the
// queue may release the last reference to the queue when invoked by
// _dispatch_queue_drain.
_dispatch_retain(dq);
dq->dq_items_head = obj;
_dispatch_wakeup(dq);
_dispatch_release(dq);
}
这里的dq是我们之前封装的ds;
// 6618342 Contact the team that owns the Instrument DTrace probe before renaming this symbol
dispatch_queue_t
_dispatch_wakeup(dispatch_object_t dou)
{
dispatch_queue_t tq;
if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
return NULL;
}
if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
return NULL;
}
if (!_dispatch_trylock(dou._do)) {
return NULL;
}
_dispatch_retain(dou._do);
tq = dou._do->do_targetq;
_dispatch_queue_push(tq, dou._do);
return tq; // libdispatch doesn't need this, but the Instrument DTrace probe does
}
看看dispatch_source_create在创建ds为ds设置的值:
这个流程的结论就是:
dq->dq_items_head = obj;
将obj的任务插入到了dq的头部中;
而之后的 dispatch_source_set_cancel_handler_f(ds, _dispatch_after_timer_cancel); 也就是将新的任务插入在上面ds的head的next的位置,然后return了;
并没有调度ds;