1 简介

dispatch source是一种用于处理事件的数据类型,这些被处理的事件为操作系统中的底层级别。Grand Central Dispatch(GCD)支持如下的dispatch sources类型:

  1. Timer dispatch sources:定时器类型,能够产生周期性的通知事件;
  2. Signal dispatch sources:信号类型,当UNIX信号到底时,能够通知应用程序;
  3. Descriptor sources:文件描述符类型,处理UNIX的文件或socket描述符,如:
    • 数据可读
    • 数据可写
    • 文件被删除、修改或移动
    • 文件的元信息被修改
  4. Process dispatch sources:进程类型,能够通知一些与进程相关的事件类型,如:
    • 当进程退出
    • 当进程调用了fork或exec
    • 当一个信号传递给了进程
  5. Mach port dispatch sources:端口匹配类型,能够通知一些端口事件的类型;
  6. Custom dispatch sources:自定义类型,可以自定义一些事件类型。

Dispatch sources能够替换一些异步的回调函数,特别是用于处理一些与系统相关的事件。当进行dispatch source配置时,可以指定希望监控的事件类型,且可以指定dispatch queue和代码来处理上述的事件,代码的形式有block对象或函数。当一个感兴趣的事件到达时,那么所指定的block或函数将会被调用核执行。

与将任务提交到GCD dispatch queue不同,dispatch sources将会持续对所提交的事件进行监控,除非精确取消所感兴趣的事件。

为了防止事件被积压在dispatch queue中,dispatch sources实现了一种事件合并机制。如果在上一个事件被放进队列和被执行之前,又来了一个新事件,则dispatch source将合并老事件和新事件。合并可能会替换或更新事件的信息,这完全依赖事件的类型。这种机制与UNIX系统信号的不排队机制是一样的。

2 创建Dispatch Sources

创建一个dispatch Sources将涉及两方面的创建过程:创建源事件和dispatch Sources对象。在创建了源事件之后,则可以按如下的步骤创建dispatch Sources对象:

  1. 使用dispatch_source_create函数来创建dispatch Sources对象;
  2. 配置dispatch Sources对象:
  • 为dispatch Sources对象指定一个事件处理句柄;
  • 若是timer sources类型的事件,则可以调用dispatch_source_set_timer函数来设置timer信息。
  1. 配置dispatch source对象的取消句柄,这为可选操作;
  2. 调用dispatch_resume函数开始进行事件的处理。

在一个dispatch sources对象被使用之前,需要对其进行一个附加的配置操作,因为当调用dispatch_source_create函数来创建一个dispatch sources对象后,该对象仍处于suspended(挂起)状态。处于挂起状态的dispatch sources对象是可以接收事件的,但不能这些处理事件。这种机制给了用户时间来配置事件的处理句柄和执行一些附件的配置操作。

2.1 配置Event Handler

为了处理dispatch sources对象所产生的事件,用户必须定义一个event handler(事件处理句柄)来执行这些事件。一个事件处理句柄可以是一个block对象或是一个函数,可以使用dispatch_source_set_event_handler 和 dispatch_source_set_event_handler_f函数来配置事件处理句柄。从而当一个事件到底时,dispatch source对象会将事件处理句柄投放到dispatch queue中进行执行。

事件处理句柄体的内容负责处理任何到底的事件。如果当一个新事件到达时,而前一个事件处理句柄虽被放入队列,但还未被执行,那么dispatch source将合并两个事件;如果当一个或多个事件到达时,前一个事件的处理句柄已经开始执行,则dispatch source将保存这些事件,直到当前的处理句柄执行后,dispatch source再将事件处理句柄投入队列中。

如下所示是block和函数的声明,函数有个参数,可以通过该参数获取一些上下文信息;而block没有任何参数,只能通过block之外的对象获取相关的信息。

1 // Block-based event handler
2 void (^dispatch_block_t)(void)
3 // Function-based event handler
4 void (*dispatch_function_t)(void *)

Function

Description

dispatch_source_get_handle

这个函数返回一个dispatch source监控的数据结构,根据不同的dispatch source类型,则返回的不同语义:

若是描述符类型,则返回一个int类型的文件描述符。

若是信号类型,则返回一个int类型的信号数字。

若是进程类型,则返回一个pid_t类型的数据结构。

若是端口类型,则返回一个端口号。

若是其它类型,则返回的值是不确定的。

dispatch_source_get_data

 

dispatch_source_get_mask

 

比如如下:

1 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
2 myDescriptor, 0, myQueue);
3 dispatch_source_set_event_handler(source, ^{
4 // Get some data from the source variable, which is captured
5 // from the parent context.
6 size_t estimated = dispatch_source_get_data(source);
7 // Continue reading the descriptor...
8 });
9 dispatch_resume(source);

2.2 配置Cancellation Handler

Cancellation handlers(取消处理句柄)用于dispatch source对象释放之前对其内部资源进行清理操作,对于大多数dispatch source对象都不需要配置Cancellation handlers,仅仅当进行了一些自定义的行为时,才需要。但如果用dispatch source对象来处理descriptor 和 Mach port时,则必须配置Cancellation handlers来关闭文件描述符和端口号。

可以在任何时候配置Cancellation handlers,但一般情况是在创建了dispatch source对象之后进行配置。根据block和函数的不同,可以使用dispatch_source_set_cancel_handler 或dispatch_source_set_cancel_handler_f函数进行配置。

如下的例子是进行文件描述符关闭的Cancellation handlers配置操作。

1 dispatch_source_set_cancel_handler(mySource, ^{
2 close(fd); // Close a file descriptor opened earlier.
3 });

2.3 修改目标queue

在创建了dispatch source对象是会 指定event 和 cancellation handlers运行的queue,之后也可以通过dispatch_set_target_queue函数修改运行的queue。但这种改变最好尽快修改,如果一个event handler已经进行排队和等待运行,则该event handler将仍在前一个queue中执行。然而在修改queue之后到达的事件将在新配置的queue中执行。

2.4 内存管理

类似其它的dispatch对象,dispatch source对象也拥有引用计数,其在创建时将其引用计数值初始化为1,其后可以通过dispatch_retain 和 dispatch_release来改变引用计数值。

3 Dispatch Source例子

3.1 Timer

timer为一种定时器事件类型,它能周期性产生事件。但当计算机进入sleep状态时,将暂停所有的timer dispatch source对象,直到计算机恢复后才能恢复dispatch source对象。当使用dispatch_time函数DISPATCH_TIME_NOW常量来设置dispatch source对象时,则timer dispatch source将采用系统默认的时钟周期来触发事件;如果采用dispatch_walltime函数来设置dispatch source对象,则timer dispatch source能够跟踪触发的时间。

如下的例子是每隔30s触发timer dispatch source对象,其触发偏差为1s,并且在启动dispatch source后立即触发timer:

 1 dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue,dispatch_block_t block)
 2 {
 3     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
 4     if (timer)
 5     {
 6         dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval,leeway);
 7         dispatch_source_set_event_handler(timer, block);
 8         dispatch_resume(timer);
 9     }
10     return timer;
11 }
12 void MyCreateTimer()
13 {
14     dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,1ull * NSEC_PER_SEC, dispatch_get_main_queue(),
15                                                    ^{ MyPeriodicTask(); });
16     // Store it somewhere for later use.
17     if (aTimer)
18     {
19         MyStoreTimer(aTimer);
20     }
21 }
      可以使用dispatch_after 或 dispatch_after_f函数来等待一段时间到达后执行一个block或函数,这个时间值可以是相对的或是绝对的,可以根据自己的需要设置。

3.2 Reading Descriptor

为了从文件或网络中读取数据,必须打开一个file或socket,并创建一个DISPATCH_SOURCE_TYPE_READ类型的dispatch source对象。不管什么时候,都不应该把文件描述符配置为阻塞类型的操作。如下是配置一个dispatch source对象来处理读文件事件:

 1 dispatch_source_t ProcessContentsOfFile(const char* filename)
 2 {
 3     // Prepare the file for reading.
 4     int fd = open(filename, O_RDONLY);
 5     if (fd == -1)
 6         return NULL;
 7     fcntl(fd, F_SETFL, O_NONBLOCK); // Avoid blocking the read operation
 8     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 9     dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
10     if (!readSource)
11     {
12         close(fd);
13         return NULL;
14     }
15     // Install the event handler
16     dispatch_source_set_event_handler(readSource, ^{
17         size_t estimated = dispatch_source_get_data(readSource) + 1;
18         // Read the data into a text buffer.
19         char* buffer = (char*)malloc(estimated);
20         if (buffer)
21         {
22             ssize_t actual = read(fd, buffer, (estimated));
23             Boolean done = MyProcessFileData(buffer, actual); // Process the data.
24             free(buffer); // Release the buffer when done.
25             if (done) // If there is no more data, cancel the source.
26                 dispatch_source_cancel(readSource);
27         }
28     });
29     dispatch_source_set_cancel_handler(readSource, ^{close(fd);}); // Install the cancellation handler
30     dispatch_resume(readSource); // Start reading the file.
31     return readSource;
32 }

3.3 Writing Descriptor

写文件描述符与读文件描述符类似,在配置了写文件描述符后,可创建DISPATCH_SOURCE_TYPE_WRITE类似的dispatch source对象。一旦创建了dispatch source对象之后,系统将立即调用event handler来写入数据到file或socket。当完成了写数据,则可以调用dispatch_source_cancel函数来取消dispatch source对象。同样不应该将文件描述符配置为阻塞类型的操作。如下是配置一个dispatch source对象来处理写文件事件:

 1 dispatch_source_t WriteDataToFile(const char* filename)
 2 {
 3     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
 4     if (fd == -1)
 5         return NULL;
 6     fcntl(fd, F_SETFL); // Block during the write.
 7     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 8     dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);
 9     if (!writeSource)
10     {
11         close(fd);
12         return NULL;
13     }
14     dispatch_source_set_event_handler(writeSource, ^{
15         size_t bufferSize = MyGetDataSize();
16         void* buffer = malloc(bufferSize);
17         size_t actual = MyGetData(buffer, bufferSize);
18         write(fd, buffer, actual);
19         free(buffer);
20         dispatch_source_cancel(writeSource); // Cancel and release the dispatch source when done.
21     });
22     dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
23     dispatch_resume(writeSource);
24     return (writeSource);
25 }

3.4 File-System Object

如果希望监控文件系统中对象的变化,可以创建DISPATCH_SOURCE_TYPE_VNODE类型的dispatch source对象,从而当一个文件被删除、写入或重命名等操作时,能够得到通知。如下例子为监控文件名字的变化:

 1 dispatch_source_t MonitorNameChangesToFile(const char* filename)
 2 {
 3     int fd = open(filename, O_EVTONLY);
 4     if (fd == -1)
 5         return NULL;
 6     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 7     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
 8                                                       fd, DISPATCH_VNODE_RENAME, queue);
 9     if (source)
10     {
11         // Copy the filename for later use.
12         int length = strlen(filename);
13         char* newString = (char*)malloc(length + 1);
14         newString = strcpy(newString, filename);
15         dispatch_set_context(source, newString);
16         // Install the event handler to process the name change
17         dispatch_source_set_event_handler(source, ^{
18             const char* oldFilename = (char*)dispatch_get_context(source);
19             MyUpdateFileName(oldFilename, fd);
20         });
21         // Install a cancellation handler to free the descriptor
22         // and the stored string.
23         dispatch_source_set_cancel_handler(source, ^{
24             char* fileStr = (char*)dispatch_get_context(source);
25             free(fileStr);
26             close(fd);
27         });
28         // Start processing events.
29         dispatch_resume(source);
30     }
31     else
32         close(fd);
33     return source;
34 }

3.5 Signals

可以使用UNIX系统的sigaction函数来配置信号处理句柄,只要信号一到达就能立即进行处理。如果仅仅只是希望通知信号的到达,而不是真正想处理信号,则可以使用dispatch source来异步处理信号。

signal dispatch source不可以替代sigaction函数来配置信号处理句柄,sigaction配置的处理句柄能够接收到信号并防止应用程序被终止,signal dispatch source对象仅允许监控信号的到达,它不能用于查询所有的signal类型,特别是不能监控SIGILL、SIGBUS和SIGSEGV信号。如下例子配置dispatch source对象来监听SIGHUP信号:

 1 void InstallSignalHandler()
 2 {
 3     // Make sure the signal does not terminate the application.
 4     signal(SIGHUP, SIG_IGN);
 5     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 6     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
 7     if (source)
 8     {
 9         dispatch_source_set_event_handler(source, ^{
10             MyProcessSIGHUP();
11         });
12         // Start processing signals
13         dispatch_resume(source);
14     }
15 }

3.6 Process

Process dispatch source对象可以监控子进程的行为,并进行合适的响应。如一个parent进程可以监控其子进程的行为。如下例子为子进程监控父进程的退出状态:

 1 void MonitorParentProcess()
 2 {
 3     pid_t parentPID = getppid();
 4     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 5     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,parentPID,  DISPATCH_PROC_EXIT, queue);
 6     if (source)
 7     {
 8         dispatch_source_set_event_handler(source, ^{
 9             MySetAppExitFlag();
10             dispatch_source_cancel(source);
11             dispatch_release(source);
12         });
13         dispatch_resume(source);
14     }
15 }

4 取消dispatch source

Dispatch source对象将一直保持有效状态,除非手动调用dispatch_source_cancel函数来取消它。但取消了dispatch source对象后,将不能再接收到新的事件。一般情况下是取消了dispatch source后,立即释放掉该对象,如:

1 void RemoveDispatchSource(dispatch_source_t mySource)
2 {
3     dispatch_source_cancel(mySource);
4     dispatch_release(mySource);
5 }

取消dispatch source是一个异步操作,即虽然在调用了dispatch_source_cancel函数之后,dispatch source不能再接收到任何事件,但它还可以继续处理在队列中的事件,直到在队列中的最后一个事件被执行完成后,dispatch source才会执行cancellation handler句柄。

5 暂停与恢复dispatch source

可以通过使用dispatch_suspend和 dispatch_resume函数来暂停和恢复事件传递给dispatch source对象。其中要平衡这两个函数的调用。当暂停了一个dispatch source对象之后,所有在这期间传递给dispatch source对象的事件都会被保存,但当有多个同样事件时,在dispatch source对象恢复之后,会将这些事件合并为一个再发送给dispatch source对象,这与UNIX的信号不排队机制是一样的。

05-11 15:10