GCD介紹:Dispatch_source

【轉自:GCD介紹(三): Dispatch Sourceshtml

 

何爲Dispatch Sourcesnode

  簡單來講,dispatch source是一個監視某些類型事件的對象。當這些事件發生時,它自動將一個block放入一個dispatch queue的執行例程中。併發

  說的貌似有點不清不楚。咱們到底討論哪些事件類型?app

  下面是GCD 10.6.0版本支持的事件:異步

  1. Mach port send right state changes.
  2. Mach port receive right state changes.
  3. External process state change.
  4. File descriptor ready for read.
  5. File descriptor ready for write.
  6. Filesystem node event.
  7. POSIX signal.
  8. Custom timer.
  9. Custom event.

  這是一堆頗有用的東西,它支持全部kqueue所支持的事件(kqueue是什麼?見http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什麼?見http://en.wikipedia.org/wiki/Mach_(kernel))端口、內建計時器支持(這樣咱們就不用使用超時參數來建立本身的計時器)和用戶事件。async

 

用戶事件函數

  這些事件裏面多數均可以從名字中看出含義,可是你可能想知道啥叫用戶事件。簡單地說,這種事件是由你調用dispatch_source_merge_data函數來向本身發出的信號。oop

  這個名字對於一個發出事件信號的函數來講,太怪異了。這個名字的來由是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。spa

  讓我假設一種狀況。假設一些異步執行的代碼會更新一個進度條。由於主線程只不過是GCD的另外一個dispatch queue而已,因此咱們能夠將GUI更新工做push到主線程中。然而,這些事件可能會有一大堆,咱們不想對GUI進行頻繁而累贅的更新,理想的狀況是當主線程繁忙時將全部的改變聯結起來。

  用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,咱們能夠將工做拼接起來,而後主線程能夠知道從上一次處理完事件到如今一共發生了多少改變,而後將這一整段改變一次更新至進度條。

  啥也不說了,上代碼:

 1 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
 2 dispatch_source_set_event_handler(source, ^{
 3     [progressIndicator incrementBy:dispatch_source_get_data(source)];
 4 });
 5 dispatch_resume(source);
 6  
 7 dispatch_apply([array count], globalQueue, ^(size_t index) {
 8     // do some work on data at index
 9     dispatch_source_merge_data(source, 1);
10 });

  (對於這段代碼,我很想說點什麼,我第一次用dispatch source時,我糾結了好久好久,真讓人蛋疼:Dispatch source啓動時默認狀態是掛起的,咱們建立完畢以後得主動恢復,不然事件不會被傳遞,也不會被執行

  假設你已經將進度條的min/max值設置好了,那麼這段代碼就完美了。數據會被併發處理。當每一段數據完成後,會通知dispatch source並將dispatch source data加1,這樣咱們就認爲一個單元的工做完成了。事件句柄根據已完成的工做單元來更新進度條。若主線程比較空閒而且這些工做單元進行的比較慢,那麼事件句柄會在每一個工做單元完成的時候被調用,實時更新。若是主線程忙於其餘工做,或者工做單元完成速度很快,那麼完成事件會被聯結起來,致使進度條只在主線程變得可用時才被更新,而且一次將積累的改變動新至GUI。

  如今你可能會想,聽起來卻是不錯,可是要是我不想讓事件被聯結呢?有時候你可能想讓每一次信號都會引發響應,什麼後臺的智能玩意兒通通不要。啊。。其實很簡單的,別把本身繞進去了。若是你想讓每個信號都獲得響應,那使用dispatch_async函數不就好了。實際上,使用的dispatch source而不使用dispatch_async的惟一緣由就是利用聯結的優點。

 

內建事件

  上面就是怎樣使用用戶事件,那麼內建事件呢?看看下面這個例子,用GCD讀取標準輸入:

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

相關文章
相關標籤/搜索