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

何为Dispatch Sources

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

说的貌似有点不清不楚。我们到底讨论哪些事件类型?

下面是GCD 10.6.0版本支持的事件:

  1. Mach port send right state changes.
  2. Mach port receive right state changes.
  3. External process state change.
  4. File descriptor ready for read.
  5. File descriptor ready for write.
  6. Filesystem node event.
  7. POSIX signal.
  8. Custom timer.
  9. Custom event.

这是一堆很有用的东西,它支持所有kqueue所支持的事件(kqueue是什么?见http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什么?见http://en.wikipedia.org/wiki/Mach_(kernel))端口、内建计时器支持(这样我们就不用使用超时参数来创建自己的计时器)和用户事件。

用户事件

这些事件里面多数都可以从名字中看出含义,但是你可能想知道啥叫用户事件。简单地说,这种事件是由你调用dispatch_source_merge_data函数来向自己发出的信号。

这个名字对于一个发出事件信号的函数来说,太怪异了。这个名字的来由是GCD会在事件句柄被执行之前自动将多个事件进行联结。你可以将数据“拼接”至dispatch source中任意次,并且如果dispatch queue在这期间繁忙的话,GCD只会调用该句柄一次(不要觉得这样会有问题,看完下面的内容你就明白了)。

用户事件有两种: DISPATCH_SOURCE_TYPE_DATA_ADD 和 DISPATCH_SOURCE_TYPE_DATA_OR.用户事件源有个 unsigned long data属性,我们将一个 unsigned long传入 dispatch_source_merge_data。当使用 _ADD版本时,事件在联结时会把这些数字相加。当使用 _OR版本时,事件在联结时会把这些数字逻辑与运算。当事件句柄执行时,我们可以使用dispatch_source_get_data函数访问当前值,然后这个值会被重置为0。

让我假设一种情况。假设一些异步执行的代码会更新一个进度条。因为主线程只不过是GCD的另一个dispatch queue而已,所以我们可以将GUI更新工作push到主线程中。然而,这些事件可能会有一大堆,我们不想对GUI进行频繁而累赘的更新,理想的情况是当主线程繁忙时将所有的改变联结起来。

用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,我们可以将工作拼接起来,然后主线程可以知道从上一次处理完事件到现在一共发生了多少改变,然后将这一整段改变一次更新至进度条。

啥也不说了,上代码:

  1. dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
  2. dispatch_source_set_event_handler(source, ^{
  3. [progressIndicator incrementBy:dispatch_source_get_data(source)];
  4. });
  5. dispatch_resume(source);
  6. dispatch_apply([array count], globalQueue, ^(size_t index) {
  7. // do some work on data at index
  8. dispatch_source_merge_data(source, 1);
  9. });

(对于这段代码,我很想说点什么,我第一次用dispatch source时,我纠结了很久,很是崩溃:Dispatch queue启动时默认状态是挂起的,我们创建完毕之后得主动恢复,否则事件不会被传送

假设你已经将进度条的min/max值设置好了,那么这段代码就完美了。数据会被并发处理。当每一段数据完成后,会通知dispatch source并将dispatch source data加1,这样我们就认为一个单元的工作完成了。事件句柄根据已完成的工作单元来更新进度条。若主线程比较空闲并且这些工作单元进行的比较慢,那么事件句柄会在每个工作单元完成的时候被调用,实时更新。如果主线程忙于其他工作,或者工作单元完成速度很快,那么完成事件会被联结起来,导致进度条只在主线程变得可用时才被更新,并且一次将积累的改变更新至GUI。

现在你可能会想,听起来倒是不错,但是要是我不想让事件被联结呢?有时候你可能想让每一次信号都会引起响应,什么后台的智能玩意儿统统不要。啊。。其实很简单的,把你的思想放到禁锢的框子之外就行了。如果你想让每一个信号都得到响应,那使用dispatch_async函数不就行了。实际上,使用的dispatch source而不使用dispatch_async的唯一原因就是利用联结的优势。

内建事件

上面就是怎样使用用户事件,那么内建事件呢?看看下面这个例子,用GCD读取标准输入:

  1. dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2. dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
  3. STDIN_FILENO,
  4. 0,
  5. globalQueue);
  6. dispatch_source_set_event_handler(stdinSource, ^{
  7. char buf[1024];
  8. int len = read(STDIN_FILENO, buf, sizeof(buf));
  9. if(len > 0)
  10. NSLog(@"Got data from stdin: %.*s", len, buf);
  11. });
  12. dispatch_resume(stdinSource);

简单的要死!因为我们使用的是全局队列,句柄自动在后台执行,与程序的其他部分并行,这意味着对这种情况的提速:事件进入程序时,程序正在处理其他事务。

这是标准的UNIX方式来处理事务的好处,不用去写loop。如果使用经典的 read调用,我们还得万分留神,因为返回的数据可能比请求的少,还得忍受无厘头的“errors”,比如 EINTR (终端系统调用)。使用GCD,我们啥都不用管,就从这些蛋疼的情况里解脱了。如果我们在文件描述符中留下了未读取的数据,GCD会再次调用我们的句柄。

对于标准输入,这没什么问题,但是对于其他文件描述符,我们必须考虑在完成读写之后怎样清除描述符。对于dispatch source还处于活跃状态时,我们决不能关闭描述符。如果另一个文件描述符被创建了(可能是另一个线程创建的)并且新的描述符刚好被分配了相同的数字,那么你的dispatch source可能会在不应该的时候突然进入读写状态。de这个bug可不是什么好玩的事儿。

适当的清除方式是使用 dispatch_source_set_cancel_handler,并传入一个block来关闭文件描述符。然后我们使用 dispatch_source_cancel来取消dispatch source,使得句柄被调用,然后文件描述符被关闭。

使用其他dispatch source类型也差不多。总的来说,你提供一个source(mach port、文件描述符、进程ID等等)的区分符来作为diapatch source的句柄。mask参数通常不会被使用,但是对于 DISPATCH_SOURCE_TYPE_PROC 来说mask指的是我们想要接受哪一种进程事件。然后我们提供一个句柄,然后恢复这个source(前面我加粗字体所说的,得先恢复),搞定。dispatch source也提供一个特定于source的data,我们使用 dispatch_source_get_data函数来访问它。例如,文件描述符会给出大致可用的字节数。进程source会给出上次调用之后发生的事件的mask。具体每种source给出的data的含义,看man page吧。

计时器

计时器事件稍有不同。它们不使用handle/mask参数,计时器事件使用另外一个函数 dispatch_source_set_timer 来配置计时器。这个函数使用三个参数来控制计时器触发:

start参数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要dispatch_time 和  dispatch_walltime 函数来创建它们。另外,常量  DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。

interval参数没什么好解释的。

leeway参数比较有意思。这个参数告诉系统我们需要计时器触发的精准程度。所有的计时器都不会保证100%精准,这个参数用来告诉系统你希望系统保证精准的努力程度。如果你希望一个计时器没五秒触发一次,并且越准越好,那么你传递0为参数。另外,如果是一个周期性任务,比如检查email,那么你会希望每十分钟检查一次,但是不用那么精准。所以你可以传入60,告诉系统60秒的误差是可接受的。

这样有什么意义呢?简单来说,就是降低资源消耗。如果系统可以让cpu休息足够长的时间,并在每次醒来的时候执行一个任务集合,而不是不断的醒来睡去以执行任务,那么系统会更高效。如果传入一个比较大的leeway给你的计时器,意味着你允许系统拖延你的计时器来将计时器任务与其他任务联合起来一起执行。

总结

现在你知道怎样使用GCD的dispatch source功能来监视文件描述符、计时器、联结的用户事件以及其他类似的行为。由于dispatch source完全与dispatch queue相集成,所以你可以使用任意的dispatch queue。你可以将一个dispatch source的句柄在主线程中执行、在全局队列中并发执行、或者在用户队列中串行执行(执行时会将程序的其他模块的运算考虑在内)。

下一篇我会讨论如何对dispatch queue进行挂起、恢复、重定目标操作;如何使用dispatch semaphore;如何使用GCD的一次性初始化功能。

GCD教程(三):Dispatch Sources的更多相关文章

  1. GCD介绍(三): Dispatch Sources

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

  2. 深入GCD(三): Dispatch Sources

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

  3. iOS 并行编程:GCD Dispatch Sources

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

  4. GCD教程(一):基本概念

    在网上看到关于GCD的一个很不错的教程,这里做一下转载 原帖地址:http://www.dreamingwish.com/dream-2012/of-of-of-of-gcd-introduced-1 ...

  5. GCD (Grand Central Dispatch) 笔记

    GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式. 在Mac OS X 10.6和IOS 4.0之后开 ...

  6. Swift中的GCD——常见的dispatch方法

    什么是GCD Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法.该方法在Mac OS X 10.6雪豹中首次推出,并随后被引入到了iOS4.0中.GCD ...

  7. GCD学习 —— 三

    ​ 学习学习dispatch_block,在向队列中添加任务时,可以直接在对应的函数中添加 block.但是如果想对任务进行操作,比如监听任务.取消任务,就需要获取对应的 block. 1 创建Blo ...

  8. CRL快速开发框架系列教程三(更新数据)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(三)-使用Travis自动部署Hexo(1)

    前言 前面两篇文章介绍了在github上使用hexo搭建博客的基本环境和hexo相关参数设置等. 基于目前,博客基本上是可以完美运行了. 但是,有一点是不太好,就是源码同步问题,如果在不同的电脑上写文 ...

随机推荐

  1. BroadcastReceiver的两种注册方式之------动态注册

    activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...

  2. 设计模式--状态模式(分布式中间件熔断器Java实现)

    最近在做分布式服务熔断,因为要实现一个熔断器状态机,所以想到状态模式.状态模式是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类.状态模式主要解决的是当控制一个对象状态的条件表达 ...

  3. Python3基础 当函数中的局部变量与全局变量同名了,各管各的

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  4. Task 编程中的异常处理

    在 .Net 开发中, 使用 Task . Task<T> 进行异步编程是非常方便的, 但是在处理 Task 产生的异常时, 需要注意一个问题, 比如下面的代码: ? 1 2 3 4 5 ...

  5. 关于NIOS ii烧写的几种方式

    1. 方法一:.sof和.elf全部保存在FPGA内,程序加载和运行也是在FPGA内部. 把FPGA的配置文件.sof通过JTAG方式下载(其实是在线运行)进入FPGA本身,此时在NIOS II的界面 ...

  6. php 系列

    1.给 跑在windows 环境下的php, 安装redis 拓展.(installing Redis & Redis extension in PHP on XAMPP on windows ...

  7. CSS实现单行、多行文本溢出显示省略号

    单行显示省略号 overflow: hidden; text-overflow:ellipsis; white-space: nowrap;多行显示省略号 display: -webkit-box; ...

  8. Java中的数组越界问题

    Java中数组初始化和OC其实是一样的,分为动态初始化和静态初始化, 动态初始化:指定长度,由系统给出初始化值 静态初始化:给出初始化值,由系统给出长度 在我们使用数组时最容易出现的就是数组越界问题, ...

  9. NSMutableDictionary

    NSDictionary *dic = @{@"name":@"yj", @"age":@"24", @"ho ...

  10. 缩短url-url短地址链接

    之前给合作方二维码时隐藏的url过长,导致合作方提出在打印的时候打印不出来的问题,要求url长度在50字节内,所以写了缩短url功能. var url = string.Format("{0 ...