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的信号不排队机制是一样的。

iOS 并行编程:GCD Dispatch Sources的更多相关文章

  1. iOS 并行编程:GCD Dispatch Queues

    1 简介 1.1 功能          Grand Central Dispatch(GCD)技术让任务并行排队执行,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.任务可以是一个函数 ...

  2. iOS 并行编程:NSOperation Queues

    1 简介 1.1 功能        Operation Queue也是IOS的一种并行编程技术,类似Dispatch Queue可以帮助用户管理多线程.但是Operation Queue将任务封装在 ...

  3. IOS并发编程GCD

    iOS有三种多线程编程的技术 (一)NSThread  (二)Cocoa NSOperation (三)GCD(全称:Grand Central Dispatch) 这三种编程方式从上到下,抽象度层次 ...

  4. iOS 并行编程:Thread

    1 创建线程 1.1 NSThread       使用 NSThread 来创建线程有两个可以使用的方法: 1) 使用 detachNewThreadSelector:toTarget:withOb ...

  5. IOS中的多核并发编程GCD

    Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. dispatch queue分成以下三种: 1)运行在主线程的Main queue,通过dispat ...

  6. GCD介绍(三): Dispatch Sources

    何为Dispatch Sources         简单来说,dispatch source是一个监视某些类型事件的对象.当这些事件发生时,它自动将一个block放入一个dispatch queue ...

  7. GCD教程(三):Dispatch Sources

    接上一篇,原帖地址:http://www.dreamingwish.com/dream-2012/intro-to-grand-central-dispatch-part-iii-the-dispat ...

  8. 深入GCD(三): Dispatch Sources

    何为Dispatch Sources简单来说,dispatch source是一个监视某些类型事件的对象.当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中.说的 ...

  9. iOS 并发编程指南

    iOS Concurrency Programming Guide iOS 和 Mac OS 传统的并发编程模型是线程,不过线程模型伸缩性不强,而且编写正确的线程代码也不容易.Mac OS 和 iOS ...

随机推荐

  1. DedeCms 5.7友情链接模块注入漏洞

    漏洞版本: DedeCms 5.7 漏洞描述: DedeCms基于PHP+MySQL的技术开发,是目前国内应用最广泛的php类CMS系统. DedeCms 5.7前台提交友情链接处,可以插入恶意JS代 ...

  2. HTML特殊字符大全2

    HTML的特殊字符我们并不常用,但是有的时候却要在页面中用到这些字符,甚至有时候还需要用这些字符来实现某种特殊的视觉效果.现在,国外的设计师Neal Chester整理了一份很全的特殊字符集,我觉得这 ...

  3. Memcached 两款.NET客户端的郁闷事儿

    不久以后就要负责一个比较大的项目,有多大?反正就是挺大的.现在处于筹备阶段,我主要负责系统框架搭建,在系统缓存这一块决定采用Http运行时缓存+memcached. memcached 以前用过几次 ...

  4. 谈谈分布式事务之三: System.Transactions事务详解[下篇]

    在前面一篇给出的Transaction的定义中,信息的读者应该看到了一个叫做DepedentClone的方法.该方法对用于创建基于现有Transaction对 象的“依赖事务(DependentTra ...

  5. 【翻译】Selenium IDE v1.0.11 支持转换格式吗?

    原文: http://blog.reallysimplethoughts.com/2011/06/10/does-selenium-ide-v1-0-11-support-changing-forma ...

  6. selenium1.0和selenium2.0页面等待处理详解

    一.selenium1.0页面等待 1.……AndWait 经常会看到, selenium action命令中很多有这种……AndWait后缀, 例如click和clickAndWait命令: cli ...

  7. JZ2440开发笔记(3)——配置TFTP

    第一步: 1.关闭ubuntu的防火墙 ufw disable2.卸载了iptables         apt-get remove iptables 1.用iptables -F这个命令来关闭防火 ...

  8. kernel网址

    http://www.kernel.org HTTP https://www.kernel.org/pub/ FTP ftp://ftp.kernel.org/pub/ http://www.oldl ...

  9. 【解决】python2.x版本的Django下admin管理页面css无效

    折腾一下午,终于解决了这个问题,有必要记录一下,我就奇怪了为什么实验室电脑没问题,到宿舍就挂掉了,哼 主要是改mimetypes文件,位于D:\MySoftware\Python27\Lib下 1.添 ...

  10. IntelliJ 直接编辑国际化文件(properties)方法

    IntelliJ 直接编辑国际化文件(properties)方法 settings-File Encodings 右下角 Transparent native-to-ascii conversion的 ...