GCD介绍:Dispatch_source

【转自:GCD介绍(三): Dispatch Sourceshtml

 

何为Dispatch Sourcesnode

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

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

  下面是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))端口、内建计时器支持(这样咱们就不用使用超时参数来建立本身的计时器)和用户事件。async

 

用户事件函数

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

  这个名字对于一个发出事件信号的函数来讲,太怪异了。这个名字的来由是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。spa

  让我假设一种状况。假设一些异步执行的代码会更新一个进度条。由于主线程只不过是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  
 7 dispatch_apply([array count], globalQueue, ^(size_t index) {
 8     // do some work on data at index
 9     dispatch_source_merge_data(source, 1);
10 });

  (对于这段代码,我很想说点什么,我第一次用dispatch source时,我纠结了好久好久,真让人蛋疼:Dispatch source启动时默认状态是挂起的,咱们建立完毕以后得主动恢复,不然事件不会被传递,也不会被执行

  假设你已经将进度条的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的一次性初始化功能。

相关文章
相关标签/搜索