1 簡介
dispatch source是一種用於處理事件的數據類型,這些被處理的事件為操作系統中的底層級別。Grand Central Dispatch(GCD)支持如下的dispatch sources類型:
-
Timer dispatch sources:定時器類型,能夠產生周期性的通知事件;
-
Signal dispatch sources:信號類型,當UNIX信號到底時,能夠通知應用程序;
-
Descriptor sources:文件描述符類型,處理UNIX的文件或socket描述符,如:
-
數據可讀
-
數據可寫
-
文件被刪除、修改或移動
-
文件的元信息被修改
-
-
Process dispatch sources:進程類型,能夠通知一些與進程相關的事件類型,如:
-
當進程退出
-
當進程調用了fork或exec
-
當一個信號傳遞給了進程
-
-
Mach port dispatch sources:端口匹配類型,能夠通知一些端口事件的類型;
-
Custom dispatch sources:自定義類型,可以自定義一些事件類型。
Dispatch sources能夠替換一些異步的回調函數,特別是用於處理一些與系統相關的事件。當進行dispatch source配置時,可以指定希望監控的事件類型,且可以指定dispatch queue和代碼來處理上述的事件,代碼的形式有block對象或函數。當一個感興趣的事件到達時,那么所指定的block或函數將會被調用核執行。
與將任務提交到GCD dispatch queue不同,dispatch sources將會持續對所提交的事件進行監控,除非精確取消所感興趣的事件。
為了防止事件被積壓在dispatch queue中,dispatch sources實現了一種事件合並機制。如果在上一個事件被放進隊列和被執行之前,又來了一個新事件,則dispatch source將合並老事件和新事件。合並可能會替換或更新事件的信息,這完全依賴事件的類型。這種機制與UNIX系統信號的不排隊機制是一樣的。
2 創建Dispatch Sources
創建一個dispatch Sources將涉及兩方面的創建過程:創建源事件和dispatch Sources對象。在創建了源事件之后,則可以按如下的步驟創建dispatch Sources對象:
-
使用dispatch_source_create函數來創建dispatch Sources對象;
-
配置dispatch Sources對象:
-
為dispatch Sources對象指定一個事件處理句柄;
-
若是timer sources類型的事件,則可以調用dispatch_source_set_timer函數來設置timer信息。
-
配置dispatch source對象的取消句柄,這為可選操作;
-
調用dispatch_resume函數開始進行事件的處理。
在一個dispatch sources對象被使用之前,需要對其進行一個附加的配置操作,因為當調用dispatch_source_create函數來創建一個dispatch sources對象后,該對象仍處於suspended(掛起)狀態。處於掛起狀態的dispatch sources對象是可以接收事件的,但不能這些處理事件。這種機制給了用戶時間來配置事件的處理句柄和執行一些附件的配置操作。
2.1 配置Event Handler
為了處理dispatch sources對象所產生的事件,用戶必須定義一個event handler(事件處理句柄)來執行這些事件。一個事件處理句柄可以是一個block對象或是一個函數,可以使用dispatch_source_set_event_handler 和 dispatch_source_set_event_handler_f函數來配置事件處理句柄。從而當一個事件到底時,dispatch source對象會將事件處理句柄投放到dispatch queue中進行執行。
事件處理句柄體的內容負責處理任何到底的事件。如果當一個新事件到達時,而前一個事件處理句柄雖被放入隊列,但還未被執行,那么dispatch source將合並兩個事件;如果當一個或多個事件到達時,前一個事件的處理句柄已經開始執行,則dispatch source將保存這些事件,直到當前的處理句柄執行后,dispatch source再將事件處理句柄投入隊列中。
如下所示是block和函數的聲明,函數有個參數,可以通過該參數獲取一些上下文信息;而block沒有任何參數,只能通過block之外的對象獲取相關的信息。
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 |
比如如下:
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配置操作。
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:
2 {
3 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, 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 }
3.2 Reading Descriptor
為了從文件或網絡中讀取數據,必須打開一個file或socket,並創建一個DISPATCH_SOURCE_TYPE_READ類型的dispatch source對象。不管什么時候,都不應該把文件描述符配置為阻塞類型的操作。如下是配置一個dispatch source對象來處理讀文件事件:
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對象來處理寫文件事件:
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對象,從而當一個文件被刪除、寫入或重命名等操作時,能夠得到通知。如下例子為監控文件名字的變化:
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信號:
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進程可以監控其子進程的行為。如下例子為子進程監控父進程的退出狀態:
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后,立即釋放掉該對象,如:
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的信號不排隊機制是一樣的。