iOS 并行编程:GCD Dispatch Sources

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或函数将会被调用核执行。 app

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

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

2 建立Dispatch Sources 函数

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

  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对象是能够接收事件的,但不能这些处理事件。这种机制给了用户时间来配置事件的处理句柄和执行一些附件的配置操做。 spa

2.1 配置Event Handler 操作系统

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

       事件处理句柄体的内容负责处理任何到底的事件。若是当一个新事件到达时,而前一个事件处理句柄虽被放入队列,但还未被执行,那么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, 00, 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的信号不排队机制是同样的。

相关文章
相关标签/搜索