`

GCD介绍(三): Dispatch Sources

阅读更多

原文地址:http://www.tanhao.me/pieces/360.html/

 

何为Dispatch Sources

简单来说,dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。
说的貌似有点不清不楚。我们到底讨论哪些事件类型?

下面是GCD 10.6.0版本支持的事件:
Mach port send right state changes.
Mach port receive right state changes.
External process state change.
File descriptor ready for read.
File descriptor ready for write.
Filesystem node event.
POSIX signal.
Custom timer.
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,我们可以将工作拼接起来,然后主线程可以知道从上一次处理完事件到现在一共发生了多少改变,然后将这一整段改变一次更新至进度条。
啥也不说了,上代码:

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
    [progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);

dispatch_apply([array count], globalQueue, ^(size_t index) {
    // do some work on data at index
    dispatch_source_merge_data(source, 1);
});

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

假设你已经将进度条的min/max值设置好了,那么这段代码就完美了。数据会被并发处理。当每一段数据完成后,会通知dispatch source并将dispatch source data加1,这样我们就认为一个单元的工作完成了。事件句柄根据已完成的工作单元来更新进度条。若主线程比较空闲并且这些工作单元进行的比较慢,那么事件句柄会在每个工作单元完成的时候被调用,实时更新。如果主线程忙于其他工作,或者工作单元完成速度很快,那么完成事件会被联结起来,导致进度条只在主线程变得可用时才被更新,并且一次将积累的改变更新至GUI。
现在你可能会想,听起来倒是不错,但是要是我不想让事件被联结呢?有时候你可能想让每一次信号都会引起响应,什么后台的智能玩意儿统统不要。啊。。其实很简单的,把你的思想放到禁锢的框子之外就行了。如果你想让每一个信号都得到响应,那使用dispatch_async函数不就行了。实际上,使用的dispatch source而不使用dispatch_async的唯一原因就是利用联结的优势。

内建事件

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

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                                       STDIN_FILENO,
                                                       0,
                                                       globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
    char buf[1024];
    int len = read(STDIN_FILENO, buf, sizeof(buf));
    if(len > 0)
        NSLog(@"Got data from stdin: %.*s", len, buf);
});
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的一次性初始化功能。

该系列文章转载自:http://www.dreamingwish.com/

分享到:
评论

相关推荐

    GCD基本概念

    5. **Dispatch Sources:** GCD还提供了一种称为dispatch sources的机制,用于处理各种类型的系统事件,比如文件描述符、进程、信号、定时器事件等。开发者可以设置事件处理器来处理这些事件。 ### GCD的应用场景 ...

    iOS 并发编程

    5. Dispatch Sources: Dispatch Sources是GCD中的另一种资源类型,它提供了一种机制来监控系统事件。这些事件可以是定时器、文件描述符的变化、信号的接收等。这种机制允许应用程序对这些事件做出异步响应,而不是...

    iOS 并发编程指南

    #### 三、Dispatch Queues **3.1 概述** 深入探讨Dispatch Queues的工作原理,包括如何创建和使用队列。 **3.2 队列的优先级和并发性** 了解不同类型的队列及其优先级和并发性如何影响任务的执行。 **3.3 异步...

    iOS并发编程指南.pdf

    iOS并发编程指南详细介绍了如何使用GCD和Operation Queues来实现高效的任务并发执行。GCD提供了基于队列的轻量级并发编程方法,而Operation Queues则提供了更面向对象的并发处理方式。此外,还涉及到Dispatch ...

    iOS并发编程指南

    GCD(Grand Central Dispatch)是Apple为多核处理器设计的一种底层并发技术。它提供了一种管理线程和任务的方式,简化了并发编程。Dispatch Queue分为串行队列和并行队列。串行队列保证任务按顺序执行,而并行队列...

    Swift4使用GCD实现计时器

    GCD通过队列(Queues)和源(Dispatch Sources)来处理异步执行和同步执行的任务。其中DispatchSourceTimer是一种专门用于计时的源,可以用来创建和管理计时器。 在本文介绍的MRGCDTimer类中,使用了DispatchSource...

    iOS并发编程指南1

    2. **Dispatch Sources**:GCD中的Dispatch Sources是一种特殊类型的任务源,用于处理来自系统事件(如文件描述符、定时器、信号等)的异步事件。它们可以在指定的队列上触发回调,简化了系统事件的处理。 3. **...

    libdispatch-1324.60.3

    3. **Dispatch Sources**:它们提供了一种机制来监听系统事件,如文件描述符、信号、定时器等,并在事件发生时自动将处理函数添加到队列。 4. **Dispatch Groups**:允许开发者组织相关的工作项,当组内的所有任务...

    swift-Swift开源项目-模仿单糖

    8. **GCD (Grand Central Dispatch)**:Swift支持Apple的GCD技术,用于并发编程,处理多线程和异步操作。 9. **Playgrounds**:Swift的Playgrounds是学习和测试代码的好工具,可以实时查看代码执行结果,方便调试和...

    Object-C期末复习_object-c_

    10. **GCD(Grand Central Dispatch)**: - GCD是Apple的多线程解决方案,用于并行执行任务,提高应用程序性能。 11. **Foundation框架与AppKit/UIKit框架**: - Foundation框架是Objective-C的基础,提供了大量...

    iOS游戏应用源代码——francis1122-BopAMole-3a6e74d.zip

    9. **性能优化**:为了保证游戏流畅运行,源代码中可能包含了内存管理和多线程优化的策略,如使用异步加载资源或利用GCD(Grand Central Dispatch)进行并发处理。 10. **测试与调试**:良好的测试覆盖是保证代码...

    iOS面试问题:iOS面试题整理,在线查看地址:https:ios.nobady.cn

    以下是一些关键知识点,这些知识点通常会在iOS面试中出现,包括Objective-C、Runtime、GCD(Grand Central Dispatch)以及RunLoop。 1. **Objective-C**: - **面向对象特性**:Objective-C是一种基于C语言的面向...

    这是一个基于Objective-C语言的基础案例集。旨在用于给初学者快速了解Objective-C语言的语法。.zip

    9. **GCD(Grand Central Dispatch)**:苹果提供的多线程解决方案,简化了并发编程,如使用队列来同步和异步执行任务。 10. **NSPredicate**:用于创建查询表达式,通常在过滤、排序和选择数据时使用。 11. **...

    APCPS创建任务

    Swift提供了多种方式来创建和管理这样的任务,包括GCD(Grand Central Dispatch)、OperationQueue和Promise/Future等。 1. **GCD(Grand Central Dispatch)**:这是Apple的多线程解决方案,用于调度并发任务。在...

    iOS游戏应用源代码——relikd-OGActionChooser-89ca761.zip

    6. **多线程管理**:为了保证游戏流畅运行,开发者可能会利用GCD(Grand Central Dispatch)或NSOperationQueue来处理后台任务,确保主线程不被阻塞。 7. **内存管理**:iOS采用ARC(Automatic Reference Counting...

    【叶-孤-城】iOS进阶指南

    ##### GCD (Grand Central Dispatch) GCD是一种多核编程模型,它简化了多线程代码的编写。在iOS开发中,合理利用GCD可以有效提升应用的响应性和性能。 1. **串行队列与并行队列**:理解它们之间的区别,并能够在...

    叶孤城】iOS进阶指南

    #### Grand Central Dispatch (GCD) - **简介**:GCD是苹果提供的一个框架,用于优化多核处理器的程序执行效率。 - **主要功能**: - 异步执行任务。 - 并发执行任务。 - 操作队列管理。 #### NSRunLoop - **...

    Recipes

    此外,Swift还引入了如闭包、泛型、GCD(Grand Central Dispatch)等高级特性,这些都是编写高效、可维护代码的关键。 Swift中的核心概念包括: 1. **变量与常量**:`var`用于声明可变变量,`let`用于声明常量。 2...

    ShoppingAlertFinal

    6. **异步编程**:GCD (Grand Central Dispatch) 和PromiseKit 可能会被用来处理异步操作。 7. **错误处理**:Swift的do-catch语句处理可能出现的错误。 8. **第三方库集成**:如使用CocoaPods或Carthage来管理项目...

Global site tag (gtag.js) - Google Analytics