Dispatch Sources

一、简介

Dispatch Sources常用于处理跟系统有关的事件,协调处理指定的低级别的系统事件。在配置Dispatch Source时,需指定监控的事件类型、Dispatch Queues、Event Handle(blocks/functions)。当被监控的事件发生时,Dispatch Source提交Event Handle到指定的Dispatch Queues。

不同于手动提交到queue中的任务,dispatch sources给应用提供了持续的事件资源。dispatch source除了明确取消,否则会持续与dispatch queue相关联。不管什么时候指定的事件发生时,就会提交任务到关联着的dispatch queue中。例如,定时器事件周期性的发生,还有大多数只有在指定条件下才发生的事件。为此,dispatch sources持有关联的dispatch queue,避免事件仍然会发生而dispatch queue被释放了。

为了避免event handle被积压在某个dispatch queue中,dispatch sources实现事件合并方案。如果前一个任务已出列并在处理时,新的事件到来了,dispatch source合并新事件和旧事件的数据。合并规则取决于事件的类型,合并可能代替旧事件,或者更新旧事件的数据。例如,基于信号的dispatch source会提供最近相关的信息,但也报告自从上次事件处理发生以来总共发出了多少信号量。

Dispatch Sources包括这几类:Timer dispatch sources、Signal dispatch sources、Descriptor sources、Process dispatch sources、Mach port dispatch sources和Custom dispatch sources。

1、Timer dispatch sources周期性通知。
2、Signal dispatch sources为unix信号发出时通知。
3、Descriptor sources各种各样的file-和socket-操作通知。如从文件或者网络中读/写数据,或文件名被重命名,或文件被删、被移动、数据内容改动时。
4、Process dispatch sources父子process退出时等等操作通知。
5、Mach port dispatch sources
6、Custom dispatch sources

二、创建Dispatch Sources

dispatch_source_create函数返回的是出于暂停状态的dispatch source,在暂停状态时,dispatch source接收通知但并不执行event handle。

1、Event Handle

event handle用于处理dispatch source的通知,通过dispatch_source_set_event_handle函数,为dispatch source创建function/block类型的event handle。当事件到来时,dispatch source提交event handle到指定的dispatch queue。

event handle为处理即将到来的所有事件负责。假设上一个event handle已经在队列中等待被执行,又有新的event handle请求添加到queue中,dispatch source会合并两个事件。然而当event handel正在执行,dispatch source等待它执行结束后,再将event handle提交到queue中。

// 基于block的event handle没有参数也没有返回值。
void (^dispatch_block_t)(void)

// 基于function的event handle包括上下文指针和dispatch source对象,无返回值。
void (*dispatch_function_t)(void *)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                 myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
   // block从外部捕获到source变量
   size_t estimated = dispatch_source_get_data(source);

   // Continue reading the descriptor...
});
dispatch_resume(source);

2、Cancellation Handle

Cancellation handle用于在dispatch source被释放前清理dispatch source。在大多数dispatch source类型中它是选择性被实现,除了descriptor/mach port dispatch source需通过cancellation handle去关闭descriptor和释放mach port。

dispatch_source_set_cancel_handler(mySource, ^{
   close(fd); // Close a file descriptor opened earlier.
});

3、Target Queue

在创建dispatch source需要指定调度event handle/cancellation handle的dispatch queue。在指定之后,还可以通过dispatch_set_target_queue函数修改关联的dispatch queue。一般修改target queue是用于修改queue的优先级,该操作是异步操作。因此在做修改操作前,已在旧dispatch queue中的任务继续被调度执行。如果恰好在修改过程中,添加任务到queue,该queue可能是旧queue,也可能是新queue。

4、Custom Data

跟GCD一样,dispatch source可以通过dispatch_set_context关联自定义数据,原理是通过context pointer存储event handle中需要用到的数据。注意的是创建了context pointer,就必须通过cancellation handle最终释放那些存储的数据。

另一种方案是通过event handle用block实现,虽然也能捕获变量,但变量随时可能被释放。因此这种方案需要通过拷贝并持有数据防止变量被回收,最终再通过cancellation handle释放该变量。

5、Memory Management

满足内存管理原则,可以通过dispatch_retain/dispatch_release来控制。

三、Dispatch Source案例

1、Create a Timer

timer dispatch source是周期性的timers,类型为DISPATCH_SOURCE_TYPE_TIMER,leeway值是设置的容差值,如果leeway为0,系统也无法保证在指定周期执行任务。它常用于游戏等应用刷新频幕和动画。

当电脑进入休眠时,timer dispatch source也被暂停,电脑恢复时它恢复,暂停会影响timer下一次执行。如果通过dispatch_time创建的timer,时间为相对时间,它会使用系统闹钟,系统闹钟在电脑休眠时不会转动。但如果通过 dispatch_walltime创建的timer,时间为绝对时间,它使用wall闹钟,常用于大的时间间隔。

dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
   dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
   if (timer){
      dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); // 时间间隔够长,所以用dispatch_walltime()函数
      dispatch_source_set_event_handler(timer, block);
      dispatch_resume(timer);
   }
   return timer;
}

void MyCreateTimer()
{
   // 每30秒执行一次,容差1秒,event handle中具体实现为MyPeriodicTask()
   dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC, dispatch_get_main_queue(), ^{ MyPeriodicTask(); });

   // Store it somewhere for later use.
    if (aTimer){
        MyStoreTimer(aTimer);
    }
}

除了timer dispatch source定期处理系统事件,还有dispatch_after在指定时间之后执行一次某事件,dispatch_after就像指定了时间的dispatch_async函数。

2、Reading Data from a Descriptor

dispatch_source_t ProcessContentsOfFile(const char* filename)
{
   // Prepare the file for reading.
   int fd = open(filename, O_RDONLY);
   if (fd == -1)
     return NULL;
   fcntl(fd, F_SETFL, O_NONBLOCK); // 避免阻塞读数据进程

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
   if (!readSource){
      close(fd);
      return NULL;
   }

   // Event Handler
   dispatch_source_set_event_handler(readSource, ^{    size_t estimated = dispatch_source_get_data(readSource) + 1;
      // 读取数据至buffer
      char* buffer = (char*)malloc(estimated);
      if (buffer){
         ssize_t actual = read(fd, buffer, (estimated));
         Boolean done = MyProcessFileData(buffer, actual);  // 处理数据
         free(buffer);
         // 读取完毕,取消该source。
         if (done)
            dispatch_source_cancel(readSource);
      }
    });

   // Cancellation Handler
   dispatch_source_set_cancel_handler(readSource, ^{close(fd);});

   // 开始读文件
   dispatch_resume(readSource);
   return readSource;
}     

3、Writing Data to a Descriptor

dispatch_source_t WriteDataToFile(const char* filename)
{
    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
    if (fd == -1)
        return NULL;
    fcntl(fd, F_SETFL); // Block during the write.

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);
    if (!writeSource){
        close(fd);
        return NULL;
    }

    dispatch_source_set_event_handler(writeSource, ^{
        size_t bufferSize = MyGetDataSize();
        void* buffer = malloc(bufferSize);

        size_t actual = MyGetData(buffer, bufferSize);
        write(fd, buffer, actual);

        free(buffer);

        // Cancel and release the dispatch source when done.
        dispatch_source_cancel(writeSource);
    });

    dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
    dispatch_resume(writeSource);
    return (writeSource);
}

4、Monitoring a File-System Object

dispatch_source_t MonitorNameChangesToFile(const char* filename)
{
   int fd = open(filename, O_EVTONLY);
   if (fd == -1)
      return NULL;

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_RENAME, queue);
   if (source){
      // Copy the filename for later use.
      int length = strlen(filename);
      char* newString = (char*)malloc(length + 1);
      newString = strcpy(newString, filename);
      dispatch_set_context(source, newString);

      // Install the event handler to process the name change
      dispatch_source_set_event_handler(source, ^{
            const char*  oldFilename = (char*)dispatch_get_context(source);
            MyUpdateFileName(oldFilename, fd);
      });

      // Install a cancellation handler to free the descriptor
      // and the stored string.
      dispatch_source_set_cancel_handler(source, ^{
          char* fileStr = (char*)dispatch_get_context(source);
          free(fileStr);
          close(fd);
      });

      // Start processing events.
      dispatch_resume(source);
   }
   else
      close(fd);

   return source;
}

5、Monitoring Signals

void InstallSignalHandler()
{
   // Make sure the signal does not terminate the application.
   signal(SIGHUP, SIG_IGN);

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);

   if (source){
      dispatch_source_set_event_handler(source, ^{
         MyProcessSIGHUP();
      });

      // Start processing signals
      dispatch_resume(source);
   }
}

6、Monitoring a Process

void MonitorParentProcess()
{
   pid_t parentPID = getppid();

   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, parentPID, DISPATCH_PROC_EXIT, queue);
   if (source){
      dispatch_source_set_event_handler(source, ^{
         MySetAppExitFlag();
         dispatch_source_cancel(source);
         dispatch_release(source);
      });
      dispatch_resume(source);
   }
}

四、取消Dispatch Source

void RemoveDispatchSource(dispatch_source_t mySource)
{
   dispatch_source_cancel(mySource);
   dispatch_release(mySource);
}

五、暂停和恢复Dispatch Source

时间: 2024-10-06 12:09:47

Dispatch Sources的相关文章

GCD介绍(三): Dispatch Sources

何为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 chang

iOS 并行编程:GCD Dispatch Sources

1 简介 dispatch source是一种用于处理事件的数据类型,这些被处理的事件为操作系统中的底层级别.Grand Central Dispatch(GCD)支持如下的dispatch sources类型: Timer dispatch sources:定时器类型,能够产生周期性的通知事件: Signal dispatch sources:信号类型,当UNIX信号到底时,能够通知应用程序: Descriptor sources:文件描述符类型,处理UNIX的文件或socket描述符,如:

GCD基本概念和Dispatch Queue

什么是GCD? Grand Central Dispatch或者GCD,是?一套低层API,提供了?一种新的?方法来进?行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将 任务切分为多个单?一任务然后提交?至?工作队列来并发地或者串?行地执?行.GCD?比之NSOpertionQueue更底层更?高效,并且它不是Cocoa框架的?一部分. 除了代码的平?行执?行能?力,GCD还提供?高度集成的事件控制系统.可以设置句柄来响应?文件描述符.mach po

GCD: 基本概念和Dispatch Queue 【转】

什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行.GCD比之 NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分. 除了代码的平行执行能力,GCD还提供高度集成的事件控制系统.可以设置句柄来响应文件描述符.mach ports(Mach port 用于

Grand Central Dispatch

[Grand Central Dispatch] GCD is one of the technologies for starting tasks asynchronously. This technology takes the thread management code you would normally write in your own applications and moves that code down to the system level. All you have t

Dispatch Queues调度队列

前言-死锁案例 // 在主线程中执行 dispatch_queue_t queueMain = dispatch_get_main_queue(); dispatch_sync(queueMain, ^{ NSLog(@"+++++++"); }); NSLog(@"hahahaha"); 案例分析:运行结果是程序阻塞在dispatch_sync()处.由于main线程执行到dispatch_sync()处,线程处于等待状态.将block任务块添加到主串行队列最后,

iOS 并行编程:GCD Dispatch Queues

1 简介 1.1 功能       Grand Central Dispatch(GCD)技术让任务并行排队执行,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节. GCD中的队列称为dispatch queue,它可以保证先进来的任务先得到执行通过它能够大大简化多线程编程.工程师只要将要执行的任务(执行代码块)放入队列中,GCD将会为需要执行的任务创建

IOS开发 GCD介绍: 基本概念和Dispatch Queue

iOS的三种多线程技术 1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØNSOperation/NSOperationQueue 面向对象的线程技术 ØGCD —— Grand Central Dispatch(派发) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术 以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Appl

GCD介绍(一):基本概念和Dispatch Queues

什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行.GCD比之NSOpertionQueue更底层更高效,并且它不是Cocoa框架的一部分. 除了代码的平行执行能力,GCD还提供高度集成的事件控制系统.可以设置句柄来响应文件描述符.mach ports(Mach port 用于 O