iOS 并发编程(三)Dispatch Sources

 Dispatch Sources

当你与底层系统交互的时候,你必须做好那个任务将会执行大量时间的准备。调用到内核或其他系统层,与发生在你进程内的调用相比,需要昂贵的上下文切换开销。因此,许多系统库提供了异步接口来允许你的代码提交一个到系统的请求,并且在请求的处理过程中,继续处理其他事情。GCD在也是基于这个普遍行为来允许你提交请求,并使用blocks和dispatch queues来接受返回结果。

About Dispatch Sources

dispatch source是与特定底层系统事件相关的基础数据类型。GCD支持以下类型的dispatch sources:

   Time dispatch sources 产生间断的定时通知

   Signal dispatch sources  当UNIX信号到来的时候会通知你

   Descriptor sources 通知你不同的文件和基于socket的操作,例如

          · 当数据到来并且准备读的时候

         ·当能够写数据的时候

         ·当文件在文件系统中被删除,移动,和重命名

         ·当文件的元信息发生变化的时候

Process dispatch sources 通知你进程相关的事件,例如

         ·当一个进程退出的时候

         ·当一个进程发出fork或exec类型的调用的时候

         ·当一个信号被传递到进程

Mach port dispatch sources 通知你Mach相关的事件

Custom dispatch sources  是你自定义的并触发

Dispatch sources 取代用于处理系统相关事件的异步函数。当你配置一个dispatch source的时候,你指定你想监控的事件以及指定dispatch queue和用于处理这些事件的代码。你可以使用block对象或函数来指定你的执行代码。当你感兴趣的事件到来的时候,dispatch source提交你的代码到指定的dispatch queue去执行。

与你手动提交到队列的任务不同,dispatch sources 为你的应用提供了连续的事件源。dispatch source会一直与dispatch queue绑定直到你可以明确的取消它。绑定之后,当相关的事件发生时,它提交相关的任务代码到dispatch queue。一些事件,例如TImer 事件,会在间隔时间发生,但是大部分是在特定条件出现的情况下发生。由于这个原因,dispatch source会retain与它关联的dispatch queue来阻止它被提前释放。

为了防止在一个dispatch queue中的事件积压。dispatch sources 实现了事件聚合。如果一个新的事件在之前的事件进入队列并执行之前到来,dispatch source会将新事件的数据和旧事件的数据聚合。根据不同类型的事件,聚合可能会取代旧的事件或者更新它所持有的信息。例如,一个基于信号的dispatch source不仅提供最近的信号,而且还报告自从上次事件处理被调用之后总共传递的信号总数。

Creating Dispatch Sources

创建dispatch source包括创建事件源以及dispatch source本身。事件的来源是处理事件所需的任何本机数据结构。例如,对于一个基于描述符的dispatch source,你可能需要打开这个描述符,而对于基于进程的源你可能需要获取进程ID。当你有了事件源,你可以如下描述来创建相关的dispatch source:

     1.使用dispatch_source_create函数来创建dispatch source

     2.配置dispatch source:

               ·给dispatch source赋予一个事件处理句柄。查看Writing and Installing an Event Handler

               ·对于定时器源,使用dispatch_source_set_timer函数来设置定时器信息。查看Creating a Timer.

     3.可以的向dispatch source赋予一个取消处理事件。查看Installing a Cancellation Handler.

     4.调用dispatch_resume函数来开始一个进程事件。查看  Suspending and Resuming Dispatch Sources.

由于dispatch source在使用之前需要额外的配置,dispatch_source_create函数返回一个挂起状态的dispatch sources。当挂起的时候,dispatch source可以接收事件,但并不处理他们。这给你时间来配置事件处理函数以及执行其他额外的配置。

下面的部分向你展示了如何配置dispatch source的不同方面。关于一个详细配置特定类型的dispatch source的案例,查看 Dispatch Source Examples.对于其他用来创建和配置dispatch source的函数,查看Grand Central Dispatch (GCD) Referece。

编写和安装事件处理程序

要处理调度源生成的事件,必须定义事件处理程序来处理这些事件。事件处理程序是使用dispatch_source_set_event_handler 或dispatch_source_set_event_handler_f在调度源上安装的函数或block 。当事件到达时,调度源将您的事件处理程序提交到指定的调度队列以进行处理。

事件处理程序的主体负责处理任何到达的事件。如果您的事件处理程序已排队并等待处理时,新事件到达,则调度源将合并这两个事件。事件处理程序通常仅查看最近事件的信息,但根据调度源的类型,它还可以获取有关已发生和已合并的其他事件的信息。如果在事件处理程序开始执行后一个或多个新事件到达,则调度源将保留这些事件,直到当前事件处理程序完成执行。此时,它会将事件处理程序提交到队列来处理新事件。

基于函数的事件处理程序采用包含调度源对象的单个上下文指针,并且不返回任何值。基于块的事件处理程序不带参数,也没有返回值。

// Block-based event handler
void (^dispatch_block_t)(void)
 
// Function-based event handler
void (*dispatch_function_t)(void *)

在事件处理程序中,您可以从调度源本身获取有关给定事件的信息。虽然基于函数的事件处理程序将函数指针作为参数传递给dispatch source,但基于块的事件处理程序必须自己捕获该指针。您可以通过正常引用包含调度源的变量来为块执行此操作。例如,以下代码片段捕获source变量,该变量在块的范围之外声明。

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                 myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
   // Get some data from the source variable, which is captured
   // from the parent context.
   size_t estimated = dispatch_source_get_data(source);
 
   // Continue reading the descriptor...
});
dispatch_resume(source);

通常在块内捕获变量以允许更大的灵活性和动态性。当然,默认情况下,捕获的变量在块内是只读的。尽管块功能支持在特定情况下修改捕获的变量,但您不应尝试在与调度源关联的事件处理程序中执行此操作。调度源始终异步执行其事件处理程序,因此您捕获的任何变量的定义范围可能在您的事件处理程序执行时消失。有关如何捕获和使用块内变量的更多信息,请参阅块编程主题

表4-1列出了可以在事件处理程序代码中调用以获取有关事件信息的函数。

 

表4-1   从调度源获取数据

功能

描述

dispatch_source_get_handle

此函数返回调度源管理的基础系统数据类型。

对于描述符调度源,此函数返回int类型的数据,与调度源关联的描述符。

对于信号调度源,此函数返回int类型的数据,最近事件的信号编号。

对于进程调度源,此函数返回pid_t,为正在监视的进程的数据结构。

对于Mach端口调度源,此函数返回mach_port_t数据结构。

对于其他调度源,此函数返回的值未定义。

dispatch_source_get_data

此函数返回与事件关联的所有待处理数据。

对于从文件读取数据的描述符调度源,此函数返回可用于读取的字节数。

对于将数据写入文件的描述符调度源,如果空间可用于写入,则此函数返回正整数。

对于监视文件系统活动的描述符调度源,此函数返回一个常量,指示发生的事件类型。有关常量列表,请参阅dispatch_source_vnode_flags_t枚举类型。

对于进程分派源,此函数返回一个常量,指示发生的事件类型。有关常量列表,请参阅dispatch_source_proc_flags_t枚举类型。

对于Mach端口调度源,此函数返回一个常量,指示发生的事件类型。有关常量列表,请参阅dispatch_source_machport_flags_t枚举类型。

对于自定义调度源,此函数返回从现有数据创建的新数据值以及传递给dispatch_source_merge_data函数的新数据。

dispatch_source_get_mask

此函数返回用于创建调度源的事件标志。

对于进程调度源,此函数返回调度源接收的事件的掩码。有关常量列表,请参阅dispatch_source_proc_flags_t枚举类型。

对于具有发送权限的Mach端口调度源,此函数返回所需事件的掩码。有关常量列表,请参阅dispatch_source_mach_send_flags_t枚举类型。

对于自定义OR调度源,此函数返回用于合并数据值的掩码。

 

有关如何为特定类型的调度源编写和安装事件处理程序的一些示例,请参阅调度源示例。

安装取消处理程序

取消处理程序用于在发布之前清理调度源。对于大多数类型的调度源,取消处理程序是可选的,仅当您有一些与调度源相关联的自定义行为(也需要更新)时才需要。但是,对于使用描述符或Mach端口的调度源,必须提供取消处理程序来关闭描述符或释放Mach端口。如果不这样做,可能会导致代码中的细微错误,这些错误是由于代码或系统的其他部分无意中重用这些结构而导致的。

您可以随时安装取消处理程序,但通常在创建调度源时也会这样做。您可以使用dispatch_source_set_cancel_handleror dispatch_source_set_cancel_handler_f函数安装取消处理程序,具体取决于您是要在实现中使用块对象还是函数。以下示例显示了一个简单的取消处理程序,该处理程序关闭为分派源打开的描述符。fd变量是包含描述符的变量。

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

要查看使用取消处理程序的调度源的完整代码示例,请参阅从描述符中读取数据。

更改目标队列

虽然在创建调度源时指定了运行事件和取消处理程序的队列,但您可以随时使用该dispatch_set_target_queue函数更改该队列。您可以这样做以更改处理调度源事件的优先级。

更改调度源的队列是一种异步操作,调度源尽最大努力尽快进行更改。如果事件处理程序已排队并等待处理,则它将在前一个队列上执行。但是,在您进行更改时到达的其他事件可以在任一队列中处理。

 

将自定义数据与调度源关联

与Grand Central Dispatch中的许多其他数据类型一样,您可以使用该dispatch_set_context功能将自定义数据与调度源相关联。您可以使用上下文指针来存储事件处理程序处理事件所需的任何数据。如果你确实在上下文指针中存储了数据,你也应该安装一个取消事件处理函数来在dispatch source不需要数据的时候来释放它。

如果使用块实现事件处理程序,则还可以捕获局部变量并在基于块的代码中使用它们。虽然这可能会减少在调度源的上下文指针中存储数据的需要,但您应该明智地使用此功能。因为调度源可能在您的应用程序中存在很长时间,所以在捕获包含指针的变量时应该小心。如果指针指向的数据可以随时解除分配,则应复制数据或保留数据以防止发生这种情况。在任何一种情况下,您都需要提供一个取消处理程序以便稍后释放数据。

调度源的内存管理

与其他调度对象一样,调度源是引用计数数据类型。调度源的初始引用计数为1,可以使用dispatch_retaindispatch_release函数保留和释放。当队列的引用计数达到零时,系统会自动释放分派源数据结构。

由于它们的使用方式,调度源的所有权可以在内部或外部管理到调度源本身。对于外部所有权,另一个对象或一段代码取得调度源的所有权,并负责在不再需要时释放它。对于内部所有权,调度源拥有自己并负责在适当的时间释放自己。虽然外部所有权非常常见,但在您希望创建自主调度源并让其管理代码的某些行为而无需进一步交互的情况下,您可以使用内部所有权。例如,如果调度源设计为响应单个全局事件,则可以让它处理该事件,然后立即退出。

调度源示例

以下部分介绍如何创建和配置一些常用的调度源。有关配置特定类型的调度源的更多信息,请参阅Grand Central Dispatch(GCD)参考

 

创建一个计时器

计时器调度源以常规的,基于时间的间隔生成事件。您可以使用计时器来启动需要定期执行的特定任务。例如,游戏和其他图形密集型应用程序可能会使用计时器来启动屏幕或动画更新。您还可以设置计时器并使用生成的事件来检查频繁更新的服务器上的新信息。

所有计时器调度源都是间隔计时器 - 也就是说,一旦创建,它们就会以您指定的间隔提供常规事件。创建计时器调度源时,必须指定的值之一是leeway值,以使系统了解计时器事件所需的准确度。Leeway的价值使系统在管理电源和唤醒核心方面具有一定的灵活性。例如,系统可能会使用leeway值来提前或延迟开火时间,并使其与其他系统事件更好地对齐。因此,您应该尽可能为自己的计时器指定一个余地值。

注意:  即使您指定leeway值为0,也不应期望计时器以您请求的精确纳秒触发。系统尽力满足您的需求,但无法保证准确的发射时间。

 

当计算机进入睡眠状态时,所有计时器调度源都将暂停。当计算机唤醒时,这些计时器调度源也会自动唤醒。根据计时器的配置,这种性质的暂停可能会影响您的计时器下次触发的时间。如果使用dispatch_time函数或DISPATCH_TIME_NOW常量设置计时器调度源,则计时器调度源使用默认系统时钟来确定何时触发。但是,计算机处于睡眠状态时,默认时钟不会前进。相比之下,使用时设置计时器调度源dispatch_walltime函数,定时器调度源将其触发时间跟踪到挂钟时间。后一种选择通常适用于其发射间隔相对较大的定时器,因为它可防止在事件时间之间发生过多漂移。

清单4-1显示了一个计时器的示例,该计时器每30秒触发一次并具有1秒的leeway。由于计时器间隔相对较大,因此使用该dispatch_walltime函数创建调度源。计时器的第一次发射立即发生,后续事件每30秒到达一次。该MyPeriodicTaskMyStoreTimer符号代表了自定义定时行为,用来在一些在应用程序中的数据结构保存计时器。

清单4-1   创建一个计时器调度源

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_source_set_event_handler(timer, block);
      dispatch_resume(timer);
   }
   return timer;
}
 
void MyCreateTimer()
{
   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);
    }
}

虽然创建计时器调度源是接收基于时间的事件的主要方式,但也有其他选项可用。如果要在指定的时间间隔后执行一次块,则可以使用dispatch_afterdispatch_after_f功能。此函数的行为与dispatch_async函数非常相似,只是它允许您指定将块提交到队列的时间值。时间值可以根据您的需要指定为相对或绝对时间值。

从描述符中读取数据

要从文件或套接字读取数据,必须打开文件或套接字并创建DISPATCH_SOURCE_TYPE_READ类型的调度源。您指定的事件处理程序应该能够读取和处理文件描述符的内容。对于文件,这相当于读取文件数据(或该数据的子集)并为您的应用程序创建适当的数据结构。对于网络套接字,这涉及处理新接收的网络数据。

无论何时读取数据,都应始终将描述符配置为使用非阻塞操作。虽然您可以使用该dispatch_source_get_data函数查看可用于读取的数据量,但该函数返回的数字可能会在您进行调用的时间与实际读取数据的时间之间发生变化。如果基础文件被截断或发生网络错误,则从阻塞当前线程的描述符读取可能会在执行中期停止事件处理程序并阻止调度队列调度其他任务。对于串行队列,这可能使您的队列死锁,即使对于并发队列,这也会减少可以启动的新任务的数量。

清单4-2显示了一个配置调度源以从文件读取数据的示例。在此示例中,事件处理程序将指定文件的全部内容读入缓冲区,并调用自定义函数(您将在自己的代码中定义)来处理数据。(一旦读取操作完成,此函数的调用者将使用返回的调度源取消它。)为确保在没有要读取的数据时调度队列不会不必要地阻塞,此示例使用该fcntl函数来配置文件用于执行非阻塞操作的描述符。安装在调度源上的取消处理程序可确保在读取数据后关闭文件描述符。

 

清单4-2   从文件中读取数据


 

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);  // Avoid blocking the read operation
 
   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;
   }
 
   // Install the event handler
   dispatch_source_set_event_handler(readSource, ^{
      size_t estimated = dispatch_source_get_data(readSource) + 1;
      // Read the data into a text buffer.
      char* buffer = (char*)malloc(estimated);
      if (buffer)
      {
         ssize_t actual = read(fd, buffer, (estimated));
         Boolean done = MyProcessFileData(buffer, actual);  // Process the data.
 
         // Release the buffer when done.
         free(buffer);
 
         // If there is no more data, cancel the source.
         if (done)
            dispatch_source_cancel(readSource);
      }
    });
 
   // Install the cancellation handler
   dispatch_source_set_cancel_handler(readSource, ^{close(fd);});
 
   // Start reading the file.
   dispatch_resume(readSource);
   return readSource;
}

 在前面的示例中,自定义MyProcessFileData函数确定何时读取了足够的文件数据并且可以取消调度源。默认情况下,配置为从描述符读取的调度源会重复调度其事件处理程序,同时仍有要读取的数据。如果套接字连接关闭或您到达文件末尾,则调度源会自动停止调度事件处理程序。如果您知道不需要调度源,可以自己直接取消。

将数据写入描述符

将数据写入文件或套接字的过程与读取数据的过程非常相似。为写入操作配置描述符后,您将创建一个DISPATCH_SOURCE_TYPE_WRITE类型的调度源。一旦创建了调度源,系统就会调用您的事件处理程序,使其有机会开始将数据写入文件或套接字。完成数据写入后,使用该dispatch_source_cancel功能取消调度源。

无论何时写入数据,都应始终配置文件描述符以使用非阻塞操作。虽然您可以使用该dispatch_source_get_data函数来查看可用于写入的空间量,但该函数返回的值仅供参考,可能会在您进行调用的时间与实际写入数据的时间之间发生变化。如果发生错误,将数据写入阻塞文件描述符可能会在执行中期停止事件处理程序并阻止调度队列调度其他任务。对于串行队列,这可能使您的队列死锁,即使对于并发队列,这也会减少可以启动的新任务的数量。

清单4-3显示了使用调度源将数据写入文件的基本方法。创建新文件后,此函数将生成的文件描述符传递给其事件处理程序。放入文件的数据由MyGetData函数提供,您可以使用生成文件数据所需的任何代码替换该函数。将数据写入文件后,事件处理程序取消调度源以防止再次调用它。然后,调度源的所有者将负责释放它。

清单4-3   将数据写入文件

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)
        返回NULL;
    fcntl(fd,F_SETFL); //写入时阻塞。
 
    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)
    {
        关闭(FD);
        返回NULL;
    }
 
    dispatch_source_set_event_handler(writeSource,^ {
        size_t bufferSize = MyGetDataSize();
        void * buffer = malloc(bufferSize);
 
        size_t actual = MyGetData(buffer,bufferSize);
        写(fd,buffer,actual);
 
        自由(缓冲液);
 
        //完成后取消并释放调度源。
        dispatch_source_cancel(writeSource);
    });
 
    dispatch_source_set_cancel_handler(writeSource,^ {close(fd);});
    dispatch_resume(writeSource);
    return(writeSource);
}

监视文件系统对象

如果要监视文件系统对象的更改,可以设置DISPATCH_SOURCE_TYPE_VNODE类型的调度源。您可以使用此类型的调度源在删除,写入或重命名文件时接收通知。当文件的特定类型的元信息(例如其大小和链接数)发生变化时,您还可以使用它来发出警报。

注意:当源本身正在处理事件  您,为调度源指定的文件描述符必须保持打开状态。

 

清单4-4显示了一个示例,它监视文件的名称更改并执行某些自定义行为。(您将提供实际行为来代替MyUpdateFileName示例中调用的函数。)因为描述符是专门为调度源打开的,所以调度源包括一个关闭描述符的取消处理程序。由于示例创建的文件描述符与基础文件系统对象相关联,因此可以使用此相同的调度源来检测任意数量的文件名更改。

Listing 4-4  Watching for filename changes

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;
}

监控信号

UNIX信号允许从域外部操纵应用程序。应用程序可以接收许多不同类型的信号,范围从不可恢复的错误(例如非法指令)到关于重要信息的通知(例如子进程退出时)。传统上,应用程序使用该sigaction函数来安装信号处理函数,该函数在信号到达时同步处理信号。如果您只是想知道信号到达并且实际上并不想处理信号,您可以使用信号调度源异步处理信号。

信号调度源不是您使用该sigaction功能安装的同步信号处理程序的替代品。同步信号处理程序实际上可以捕获信号并防止它终止您的应用程序。信号调度源允许您仅监控信号的到达。此外,您无法使用信号调度源来检索所有类型的信号。特别是,你不能用它们来监控SIGILLSIGBUS以及SIGSEGV信号。

由于信号调度源在调度队列上异步执行,因此它们不会受到与同步信号处理程序相同的一些限制。例如,您可以从信号调度源的事件处理程序调用的函数没有限制。这种增加的灵活性的权衡是,在信号到达的时间与调度源的事件处理程序被调用的时间之间可能存在一些增加的延迟。

清单4-5显示了如何配置信号调度源来处理SIGHUP信号。调度源的事件处理程序调用该MyProcessSIGHUP函数,您将在应用程序中使用代码替换该函数以处理信号。

清单4-5   安装block来监视信号

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);
   }
}

如果您正在为自定义框架开发代码,使用信号调度源的一个优点是您的代码可以独立于与其链接的任何应用程序来监视信号。信号调度源不会干扰应用程序可能已安装的其他调度源或任何同步信号处理程序。

有关实现同步信号处理程序以及信号名称列表的更多信息,请参见signal手册页。

监控流程

流程调度源允许您监视特定流程的行为并进行适当的响应。父进程可能使用此类型的调度源来监视它创建的任何子进程。例如,父进程可以使用它来监视子进程的死亡。同样,子进程可以使用它来监视其父进程,并在父进程退出时退出。

清单4-6显示了安装调度源以监视父进程终止的步骤。当父进程终止时,调度源设置一些内部状态信息,让子进程知道它应该退出。(您自己的应用程序需要实现该MySetAppExitFlag函数来设置适当的终止标志。)因为调度源自主运行,因此拥有自己,它也会在预期程序关闭时取消并释放自己。

清单4-6   监视父进程的死亡

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_cancel功能明确取消它们。取消调度源会停止传递新事件,并且无法撤消。因此,您通常会取消调度源,然后立即释放它,如下所示:

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

 

取消调度源是异步操作。尽管在调用dispatch_source_cancel函数后未处理任何新事件,但仍会处理已由调度源处理的事件。在完成任何最终事件的处理之后,调度源执行其取消处理程序(如果存在)。

取消处理程序是您释放内存或清理代表调度源获取的任何资源的机会。如果您的调度源使用描述符或mach端口,则必须提供取消处理程序以关闭描述符或在取消时销毁端口。其他类型的调度源不需要取消处理程序,但如果您将任何内存或数据与调度源关联,则仍应提供一个。例如,如果将数据存储在调度源的上下文指针中,则应提供一个。有关取消处理程序的详细信息,请参阅安装取消处理程序。

 

暂停和恢复调度源

您可以使用dispatch_suspenddispatch_resume方法临时暂停和恢复调度源事件的传递。这些方法增加和减少调度对象的挂起计数。因此,您必须平衡每次dispatch_suspend以及dispatch_resume在事件发送恢复之前的调用。

暂停调度源时,将暂停在调度源挂起时发生的任何事件,直到队列恢复为止。当队列恢复时,不是传递所有事件,而是在传递之前将事件合并为单个事件。例如,如果您正在监视文件以进行名称更改,则传递的事件将仅包括最后一次名称更改。以这种方式合并事件可防止它们在队列中累积,并在恢复工作使您的应用程序过载。

原文https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/GCDWorkQueues/GCDWorkQueues.html#//apple_ref/doc/uid/TP40008091-CH103-SW12

你可能感兴趣的:(iOS开发)