iOS 並行編程:GCD Dispatch Sources

1 簡介 網絡

      dispatch source是一種用於處理事件的數據類型,這些被處理的事件爲操做系統中的底層級別。Grand Central Dispatch(GCD)支持以下的dispatch sources類型: 數據結構

  1. Timer dispatch sources:定時器類型,可以產生週期性的通知事件;
  2. Signal dispatch sources:信號類型,當UNIX信號到底時,可以通知應用程序;
  3. Descriptor sources:文件描述符類型,處理UNIX的文件或socket描述符,如:
    • 數據可讀
    • 數據可寫
    • 文件被刪除、修改或移動
    • 文件的元信息被修改
  4. Process dispatch sources:進程類型,可以通知一些與進程相關的事件類型,如:
    • 當進程退出
    • 當進程調用了fork或exec
    • 當一個信號傳遞給了進程
  5. Mach port dispatch sources:端口匹配類型,可以通知一些端口事件的類型;
  6. Custom dispatch sources:自定義類型,能夠自定義一些事件類型。

 

      Dispatch sources可以替換一些異步的回調函數,特別是用於處理一些與系統相關的事件。當進行dispatch source配置時,能夠指定但願監控的事件類型,且能夠指定dispatch queue和代碼來處理上述的事件,代碼的形式有block對象或函數。當一個感興趣的事件到達時,那麼所指定的block或函數將會被調用核執行。 app

與將任務提交到GCD dispatch queue不一樣,dispatch sources將會持續對所提交的事件進行監控,除非精確取消所感興趣的事件。 異步

      爲了防止事件被積壓在dispatch queue中,dispatch sources實現了一種事件合併機制。若是在上一個事件被放進隊列和被執行以前,又來了一個新事件,則dispatch source將合併老事件和新事件。合併可能會替換或更新事件的信息,這徹底依賴事件的類型。這種機制與UNIX系統信號的不排隊機制是同樣的。 socket

2 建立Dispatch Sources 函數

     建立一個dispatch Sources將涉及兩方面的建立過程:建立源事件和dispatch Sources對象。在建立了源事件以後,則能夠按以下的步驟建立dispatch Sources對象: ui

  1. 使用dispatch_source_create函數來建立dispatch Sources對象;
  2. 配置dispatch Sources對象:
  • 爲dispatch Sources對象指定一個事件處理句柄;
  • 如果timer sources類型的事件,則能夠調用dispatch_source_set_timer函數來設置timer信息。
  1. 配置dispatch source對象的取消句柄,這爲可選操做;
  2. 調用dispatch_resume函數開始進行事件的處理。

 

      在一個dispatch sources對象被使用以前,須要對其進行一個附加的配置操做,由於當調用dispatch_source_create函數來建立一個dispatch sources對象後,該對象仍處於suspended(掛起)狀態。處於掛起狀態的dispatch sources對象是能夠接收事件的,但不能這些處理事件。這種機制給了用戶時間來配置事件的處理句柄和執行一些附件的配置操做。 spa

2.1 配置Event Handler 操作系統

      爲了處理dispatch sources對象所產生的事件,用戶必須定義一個event handler(事件處理句柄)來執行這些事件。一個事件處理句柄能夠是一個block對象或是一個函數,可使用dispatch_source_set_event_handler 和 dispatch_source_set_event_handler_f函數來配置事件處理句柄。從而當一個事件到底時,dispatch source對象會將事件處理句柄投放到dispatch queue中進行執行。 3d

       事件處理句柄體的內容負責處理任何到底的事件。若是當一個新事件到達時,而前一個事件處理句柄雖被放入隊列,但還未被執行,那麼dispatch source將合併兩個事件;若是當一個或多個事件到達時,前一個事件的處理句柄已經開始執行,則dispatch source將保存這些事件,直到當前的處理句柄執行後,dispatch source再將事件處理句柄投入隊列中。

    以下所示是block和函數的聲明,函數有個參數,能夠經過該參數獲取一些上下文信息;而block沒有任何參數,只能經過block以外的對象獲取相關的信息。

1  //  Block-based event handler
2  void (^dispatch_block_t)( void)
3  //  Function-based event handler
4  void (*dispatch_function_t)( void *)

 

Function

Description

dispatch_source_get_handle

這個函數返回一個dispatch source監控的數據結構,根據不一樣的dispatch source類型,則返回的不一樣語義:

如果描述符類型,則返回一個int類型的文件描述符。

如果信號類型,則返回一個int類型的信號數字。

如果進程類型,則返回一個pid_t類型的數據結構。

如果端口類型,則返回一個端口號。

如果其它類型,則返回的值是不肯定的。

dispatch_source_get_data

 

dispatch_source_get_mask

 

 

好比以下:

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

2.2 配置Cancellation Handler

       Cancellation handlers(取消處理句柄)用於dispatch source對象釋放以前對其內部資源進行清理操做,對於大多數dispatch source對象都不須要配置Cancellation handlers,僅僅當進行了一些自定義的行爲時,才須要。但若是用dispatch source對象來處理descriptor 和 Mach port時,則必須配置Cancellation handlers來關閉文件描述符和端口號。

       能夠在任什麼時候候配置Cancellation handlers,但通常狀況是在建立了dispatch source對象以後進行配置。根據block和函數的不一樣,可使用dispatch_source_set_cancel_handler 或dispatch_source_set_cancel_handler_f函數進行配置。

       以下的例子是進行文件描述符關閉的Cancellation handlers配置操做。

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

 

2.3 修改目標queue

      在建立了dispatch source對象是會 指定event 和 cancellation handlers運行的queue,以後也能夠經過dispatch_set_target_queue函數修改運行的queue。但這種改變最好儘快修改,若是一個event handler已經進行排隊和等待運行,則該event handler將仍在前一個queue中執行。然而在修改queue以後到達的事件將在新配置的queue中執行。

 

2.4 內存管理

      相似其它的dispatch對象,dispatch source對象也擁有引用計數,其在建立時將其引用計數值初始化爲1,其後能夠經過dispatch_retain 和 dispatch_release來改變引用計數值。

3 Dispatch Source例子

3.1 Timer

    timer爲一種定時器事件類型,它能週期性產生事件。但當計算機進入sleep狀態時,將暫停全部的timer dispatch source對象,直到計算機恢復後才能恢復dispatch source對象。當使用dispatch_time函數DISPATCH_TIME_NOW常量來設置dispatch source對象時,則timer dispatch source將採用系統默認的時鐘週期來觸發事件;若是採用dispatch_walltime函數來設置dispatch source對象,則timer dispatch source可以跟蹤觸發的時間。

   以下的例子是每隔30s觸發timer dispatch source對象,其觸發誤差爲1s,而且在啓動dispatch source後當即觸發timer:

 1 dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue,dispatch_block_t block)
 2 {
 3     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 00, queue);
 4      if (timer)
 5     {
 6         dispatch_source_set_timer(timer, dispatch_walltime(NULL,  0), interval,leeway);
 7         dispatch_source_set_event_handler(timer, block);
 8         dispatch_resume(timer);
 9     }
10      return timer;
11 }
12  void MyCreateTimer()
13 {
14     dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,1ull * NSEC_PER_SEC, dispatch_get_main_queue(),
15                                                    ^{ MyPeriodicTask(); });
16      //  Store it somewhere for later use.
17       if (aTimer)
18     {
19         MyStoreTimer(aTimer);
20     }
21 }
      可使用dispatch_after 或 dispatch_after_f函數來等待一段時間到達後執行一個block或函數,這個時間值能夠是相對的或是絕對的,能夠根據本身的須要設置。

3.2 Reading Descriptor

     爲了從文件或網絡中讀取數據,必須打開一個file或socket,並建立一個DISPATCH_SOURCE_TYPE_READ類型的dispatch source對象。無論何時,都不該該把文件描述符配置爲阻塞類型的操做。以下是配置一個dispatch source對象來處理讀文件事件:

 1 dispatch_source_t ProcessContentsOfFile( const  char* filename)
 2 {
 3      //  Prepare the file for reading.
 4       int fd = open(filename, O_RDONLY);
 5      if (fd == - 1)
 6          return NULL;
 7     fcntl(fd, F_SETFL, O_NONBLOCK);  //  Avoid blocking the read operation
 8      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0);
 9     dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd,  0, queue);
10      if (!readSource)
11     {
12         close(fd);
13          return NULL;
14     }
15      //  Install the event handler
16      dispatch_source_set_event_handler(readSource, ^{
17         size_t estimated = dispatch_source_get_data(readSource) +  1;
18          //  Read the data into a text buffer.
19           char* buffer = ( char*)malloc(estimated);
20          if (buffer)
21         {
22             ssize_t actual = read(fd, buffer, (estimated));
23             Boolean done = MyProcessFileData(buffer, actual);  //  Process the data.
24              free(buffer);  //  Release the buffer when done.
25               if (done)  //  If there is no more data, cancel the source.
26                  dispatch_source_cancel(readSource);
27         }
28     });
29     dispatch_source_set_cancel_handler(readSource, ^{close(fd);});  //  Install the cancellation handler
30      dispatch_resume(readSource);  //  Start reading the file.
31       return readSource;
32 }

 

3.3 Writing Descriptor

      寫文件描述符與讀文件描述符相似,在配置了寫文件描述符後,可建立DISPATCH_SOURCE_TYPE_WRITE相似的dispatch source對象。一旦建立了dispatch source對象以後,系統將當即調用event handler來寫入數據到file或socket。當完成了寫數據,則能夠調用dispatch_source_cancel函數來取消dispatch source對象。一樣不該該將文件描述符配置爲阻塞類型的操做。以下是配置一個dispatch source對象來處理寫文件事件:

 1 dispatch_source_t WriteDataToFile( const  char* filename)
 2 {
 3      int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
 4      if (fd == - 1)
 5          return NULL;
 6     fcntl(fd, F_SETFL);  //  Block during the write.
 7      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0);
 8     dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd,  0, queue);
 9      if (!writeSource)
10     {
11         close(fd);
12          return NULL;
13     }
14     dispatch_source_set_event_handler(writeSource, ^{
15         size_t bufferSize = MyGetDataSize();
16          void* buffer = malloc(bufferSize);
17         size_t actual = MyGetData(buffer, bufferSize);
18         write(fd, buffer, actual);
19         free(buffer);
20         dispatch_source_cancel(writeSource);  //  Cancel and release the dispatch source when done.
21      });
22     dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
23     dispatch_resume(writeSource);
24      return (writeSource);
25 }

3.4 File-System Object

      若是但願監控文件系統中對象的變化,能夠建立DISPATCH_SOURCE_TYPE_VNODE類型的dispatch source對象,從而當一個文件被刪除、寫入或重命名等操做時,可以獲得通知。以下例子爲監控文件名字的變化:

 1 dispatch_source_t MonitorNameChangesToFile( const  char* filename)
 2 {
 3      int fd = open(filename, O_EVTONLY);
 4      if (fd == - 1)
 5          return NULL;
 6     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0);
 7     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
 8                                                       fd, DISPATCH_VNODE_RENAME, queue);
 9      if (source)
10     {
11          //  Copy the filename for later use.
12           int length = strlen(filename);
13          char* newString = ( char*)malloc(length +  1);
14         newString = strcpy(newString, filename);
15         dispatch_set_context(source, newString);
16          //  Install the event handler to process the name change
17          dispatch_source_set_event_handler(source, ^{
18              const  char* oldFilename = ( char*)dispatch_get_context(source);
19             MyUpdateFileName(oldFilename, fd);
20         });
21          //  Install a cancellation handler to free the descriptor
22           //  and the stored string.
23          dispatch_source_set_cancel_handler(source, ^{
24              char* fileStr = ( char*)dispatch_get_context(source);
25             free(fileStr);
26             close(fd);
27         });
28          //  Start processing events.
29          dispatch_resume(source);
30     }
31      else
32         close(fd);
33      return source;
34 }

 

3.5 Signals

      可使用UNIX系統的sigaction函數來配置信號處理句柄,只要信號一到達就能當即進行處理。若是僅僅只是但願通知信號的到達,而不是真正想處理信號,則可使用dispatch source來異步處理信號。

      signal dispatch source不能夠替代sigaction函數來配置信號處理句柄,sigaction配置的處理句柄可以接收到信號並防止應用程序被終止,signal dispatch source對象僅容許監控信號的到達,它不能用於查詢全部的signal類型,特別是不能監控SIGILL、SIGBUS和SIGSEGV信號。以下例子配置dispatch source對象來監聽SIGHUP信號:

 1  void InstallSignalHandler()
 2 {
 3      //  Make sure the signal does not terminate the application.
 4      signal(SIGHUP, SIG_IGN);
 5     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0);
 6     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP,  0, queue);
 7      if (source)
 8     {
 9         dispatch_source_set_event_handler(source, ^{
10             MyProcessSIGHUP();
11         });
12          //  Start processing signals
13          dispatch_resume(source);
14     }
15 }

 

3.6 Process

      Process dispatch source對象能夠監控子進程的行爲,並進行合適的響應。如一個parent進程能夠監控其子進程的行爲。以下例子爲子進程監控父進程的退出狀態:

 1  void MonitorParentProcess()
 2 {
 3     pid_t parentPID = getppid();
 4     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0);
 5     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,parentPID,  DISPATCH_PROC_EXIT, queue);
 6      if (source)
 7     {
 8         dispatch_source_set_event_handler(source, ^{
 9             MySetAppExitFlag();
10             dispatch_source_cancel(source);
11             dispatch_release(source);
12         });
13         dispatch_resume(source);
14     }
15 }

 

4 取消dispatch source

      Dispatch source對象將一直保持有效狀態,除非手動調用dispatch_source_cancel函數來取消它。但取消了dispatch source對象後,將不能再接收到新的事件。通常狀況下是取消了dispatch source後,當即釋放掉該對象,如:

1  void RemoveDispatchSource(dispatch_source_t mySource)
2 {
3     dispatch_source_cancel(mySource);
4     dispatch_release(mySource);
5 }

      取消dispatch source是一個異步操做,即雖然在調用了dispatch_source_cancel函數以後,dispatch source不能再接收到任何事件,但它還能夠繼續處理在隊列中的事件,直到在隊列中的最後一個事件被執行完成後,dispatch source纔會執行cancellation handler句柄。

5 暫停與恢復dispatch source

      能夠經過使用dispatch_suspend和 dispatch_resume函數來暫停和恢復事件傳遞給dispatch source對象。其中要平衡這兩個函數的調用。當暫停了一個dispatch source對象以後,全部在這期間傳遞給dispatch source對象的事件都會被保存,但當有多個一樣事件時,在dispatch source對象恢復以後,會將這些事件合併爲一個再發送給dispatch source對象,這與UNIX的信號不排隊機制是同樣的。

相關文章
相關標籤/搜索