調(diào)度源
當和底層系統(tǒng)交互時,必須花費大量時間為任務(wù)做好準備。調(diào)用內(nèi)核或者其他系統(tǒng)層需要切換上下文,這也是比在進程內(nèi)部調(diào)用昂貴的原因。因此,許多系統(tǒng)庫提供異步接口,允許您的代碼提交請求到系統(tǒng),且請求執(zhí)行時繼續(xù)做其他工作。GCD允許您使用塊和調(diào)度隊列提交請求并將結(jié)果返回到您的代碼來建立這種行為。
關(guān)于調(diào)度源
調(diào)度源是協(xié)調(diào)特殊低級別系統(tǒng)事件處理的基本數(shù)據(jù)類型。GCD支持以下類型的調(diào)度源:
- Timer(定時器)調(diào)度源,生成周期性的通知
- Signal(信號)調(diào)度源,當UNIX信號到達時通知
- Descriptor(描述符)源,通知一些基于文件和套接字的操作,例如:
- 當數(shù)據(jù)可讀取時
- 當可以寫入數(shù)據(jù)時
- 當文件系統(tǒng)中的文件被刪除、移動或者重命名時
- 當文件元信息改變時
- Process(進程)調(diào)度源,通知進程相關(guān)的事件,例如:
- 當進程退出時
- 當進程發(fā)出fork或者exec類型的調(diào)用時
- 當信號傳遞到進程時
- Mach port(馬赫端口)調(diào)度源,通知Mach相關(guān)事件
- Custom(自定義)調(diào)度源,自己定義調(diào)度源和觸發(fā)因素
調(diào)度源代替異步回調(diào)函數(shù),通常用于系統(tǒng)相關(guān)事件處理。當配置調(diào)度源時,需要指定想要監(jiān)視的事件、調(diào)度隊列和處理這些事件的代碼。處理代碼可以使用塊或函數(shù)。當感興趣的事件到達時,調(diào)度源提交塊或函數(shù)到指定的隊列來執(zhí)行。
與手動提交到隊列的任務(wù)不同,調(diào)度源為應(yīng)用程序提供連續(xù)的事件源。調(diào)度源保留其附加的調(diào)度隊列,直到明確取消它。當附加時,無論何時發(fā)生相關(guān)事件,調(diào)度源提交關(guān)聯(lián)的任務(wù)代碼到調(diào)度隊列。一些事件,例如定時器事件,固定間隔周期性的發(fā)生,但是大多數(shù)情況僅在特定條件出現(xiàn)時偶爾發(fā)生。因為這個原因,調(diào)度源保留其關(guān)聯(lián)的調(diào)度隊列,防止其過早釋放,而這時事件仍可能處于等待狀態(tài)。
為了防止事件在調(diào)度隊列中積壓,調(diào)度隊列實現(xiàn)了事件合并方案。如果新的事件在前一個事件的事件處理者已經(jīng)出隊和執(zhí)行之前到達,調(diào)度源合并新事件和舊事件的數(shù)據(jù)。根據(jù)事件類型,合并可能替換舊的事件或者更新其獲取的信息。例如,基于信號的調(diào)度源只提供最近信號的信息,但也報告從上次事件處理者調(diào)用到現(xiàn)在總共傳遞多少個信號。
創(chuàng)建調(diào)度源
創(chuàng)建調(diào)度源包括創(chuàng)建事件源和調(diào)度源本身。事件源是處理事件所需的任何本地數(shù)據(jù)結(jié)構(gòu)。例如,對于基于描述符的調(diào)度源可能需要打開描述符,對于基于線程的調(diào)度源可能需要獲得目標程序的線程ID。當有了事件源,可以創(chuàng)建相應(yīng)的調(diào)度源:
- 使用
dispatch_source_create函數(shù)創(chuàng)建調(diào)度源 - 配置調(diào)度源:
- 為調(diào)度源分配事件處理者;參閱編寫和設(shè)置事件處理者
- 對于定時器源,使用
dispatch_source_set_timer函數(shù)設(shè)置定時器信息;參閱創(chuàng)建定時器
- (可選)為調(diào)度源分配取消處理者;參閱設(shè)置取消處理者
- 調(diào)用
dispatch_resume函數(shù)開始處理事件;參閱暫停與恢復(fù)調(diào)度源
因為調(diào)度源在能夠使用之前需要一些額外配置,所以dispatch_source_create函數(shù)返回的調(diào)度源為暫停狀態(tài)。暫停時,調(diào)度源接收事件,但不處理。這給你時間來設(shè)置事件處理者和處理實際事件需要的任意額外配置。
以下章節(jié)介紹如何配置調(diào)度源,詳細示例說明如何配置特定類型的調(diào)度源,參閱調(diào)度源示例。有關(guān)用于創(chuàng)建和配置調(diào)度源的函數(shù)信息,請參閱GCD參考。
編寫和設(shè)置事件處理者
為了處理調(diào)度源生成的事件,必須定義事件處理者來處理這些事件。事件處理者是一個函數(shù)或者塊,使用dispatch_source_set_event_handler或者dispatch_source_set_event_handler_f函數(shù)將其放置到調(diào)度源上。當事件到達時,調(diào)度源提交事件處理者到指定的調(diào)度隊列進行處理。
事件處理者的主體負責處理到達的任何事件,當新事件到達時,如果事件處理者已經(jīng)入隊且等待處理一個事件,調(diào)度源合并這兩個事件。事件處理者通常只看最新事件的信息,但取決于調(diào)度源的類型,它也可能能夠獲取合并的其他事件的信息。在事件處理者開始執(zhí)行后,如果有一個或多個新的事件到達,調(diào)度源保持這些事件,直到當前事件處理者結(jié)束執(zhí)行。在那個時候,它再次提交事件處理者和新的事件到調(diào)度隊列。
基于函數(shù)的事件處理者獲取一個上下文指針,包括調(diào)度源對象,不返回值?;趬K的事件處理者不獲取參數(shù)也不返回值。
// Block-based event handler
void (^dispatch_block_t)(void)
// Function-based event handler
void (*dispatch_function_t)(void *)
在事件處理者內(nèi)部,可以從調(diào)度源本身獲取有關(guān)給定事件的信息。基于函數(shù)的事件處理者傳遞一個指向調(diào)度源的指針作為參數(shù),基于塊的事件處理者必須自己獲取那個指針??梢酝ㄟ^引用包含調(diào)度源的變量來做。例如,下面代碼片段獲取聲明在塊上下文之外的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);
在塊內(nèi)部獲取變量通常具有更大的靈活性和動態(tài)性。當然,獲取的變量默認情況下在塊中是只讀的。雖然塊支持在特殊條件下修改獲取的變量,最好不要在關(guān)聯(lián)到調(diào)度源的事件處理者中這么做。因為調(diào)度源總是異步執(zhí)行事件處理者,所以獲取的變量的定義上下文可能在事件處理者執(zhí)行的時候消失。有關(guān)在塊內(nèi)部獲取使用變量的更多信息,請參閱塊編程
下表列出了可以從事件處理者代碼中調(diào)用的函數(shù),來獲取關(guān)于事件的信息。
| 函數(shù) | 描述 |
|---|---|
| dispatch_source_get_handle | 這個函數(shù)返回調(diào)度源管理的底層系統(tǒng)數(shù)據(jù)類型。對于描述符調(diào)度源,這個函數(shù)返回一個int類型,包含關(guān)聯(lián)到調(diào)度源的描述符。對于信號調(diào)度源,這個函數(shù)返回一個int類型,包含最新事件的信號編號。對于進程調(diào)度源,這個函數(shù)返回被監(jiān)視進程的pid_t數(shù)據(jù)結(jié)構(gòu)。對于Mach端口調(diào)度源,這個函數(shù)返回mach_port_t數(shù)據(jù)結(jié)構(gòu)。對于其他調(diào)度源,這個函數(shù)返回的值未定義。 |
| dispatch_source_get_data | 此函數(shù)返回與事件相關(guān)聯(lián)的任何待處理數(shù)據(jù)。對于從文件中讀取數(shù)據(jù)的描述符調(diào)度源,這個函數(shù)返回可用于讀取的字節(jié)數(shù)。對于向文件中寫入數(shù)據(jù)的描述符調(diào)度源,如果空間可用于寫入,這個函數(shù)返回一個正整數(shù)。對于監(jiān)視文件系統(tǒng)活動的描述符調(diào)度源,這個函數(shù)返回一個常量,標示事件發(fā)生的類型,有關(guān)常量的列表,請參閱 dispatch_source_vnode_flags_t 枚舉類型。對于進程調(diào)度源,這個函數(shù)返回一個常量,標示事件發(fā)生的類型,有關(guān)常量的列表,請參閱dispatch_source_proc_flags_t枚舉類型。對于Mach端口調(diào)度源,這個函數(shù)返回一個常量,標示事件發(fā)生的類型,有關(guān)常量的列表,請參閱dispatch_source_machport_flags_t枚舉類型。對于自定義調(diào)度源,此函數(shù)返回由現(xiàn)有數(shù)據(jù)和傳遞到dispatch_source_merge_data函數(shù)的新數(shù)據(jù)所創(chuàng)建的新數(shù)據(jù)。 |
| dispatch_source_get_mask | 這個函數(shù)返回用于創(chuàng)建調(diào)度源的事件標志。對于線程調(diào)度源,這個函數(shù)返回調(diào)度源接收的事件掩碼,有關(guān)常量的列表,請參閱dispatch_source_proc_flags_t枚舉類型。對于有發(fā)送權(quán)限的Mach端口調(diào)度源,這個函數(shù)返回所需事件的掩碼,有關(guān)常量的列表,請參閱dispatch_source_mach_send_flags_t枚舉類型。對于自定義OR調(diào)度源,這個函數(shù)返回用于合并數(shù)據(jù)值的掩碼。 |
有關(guān)如何為特殊類型的調(diào)度源編寫和設(shè)置事件處理者的例子,請參閱調(diào)度源示例
設(shè)置取消處理者
取消處理者用于在釋放前清理調(diào)度源。對于大多數(shù)類型的調(diào)度源,清理處理者是可選的,只有當有一些自定義行為與需要更新的調(diào)度源綁定時才需要。對于使用描述符和Mach端口的調(diào)度源,必須提供取消處理者來關(guān)閉描述符或者釋放Mach端口。如果不這么做,可能導(dǎo)致代碼中出現(xiàn)細微的bug,因為這些結(jié)構(gòu)會被您的代碼或者系統(tǒng)其他部分無意中重用。
可以在任何時候設(shè)置取消處理者,但通常在創(chuàng)建調(diào)度源的時候就這么做了。使用dispatch_source_set_cancel_handler或dispatch_source_set_cancel_handler_f函數(shù)設(shè)置取消處理者,這取決與是使用塊還是函數(shù)實現(xiàn)。下面是一個簡單的取消處理者例子,用來關(guān)閉被調(diào)度源打開的描述符。變量fd是獲取的包含描述符的變量。
dispatch_source_set_cancel_handler(mySource, ^{
close(fd); // Close a file descriptor opened earlier.
});
調(diào)度源使用取消處理者的完整代碼例子,請參閱從描述符讀取數(shù)據(jù)
更改目標隊列
創(chuàng)建調(diào)度源時雖然指定隊列來運行事件處理者和取消處理者,但隨時可以使用dispatch_set_target_queue函數(shù)改變隊列??赡苓@么做來改變調(diào)度源事件處理的優(yōu)先級。
改變調(diào)度源的隊列是一個異步操作,且調(diào)度源盡可能快速的完成改變。如果一個事件處理者已經(jīng)入隊且等待被處理,那么它在前一個隊列上執(zhí)行。然而,在改變時到達的其他事件會在其他隊列上執(zhí)行。
關(guān)聯(lián)自定義對象與調(diào)度源
和GCD中很多其他數(shù)據(jù)類型一樣,可以使用dispatch_set_context函數(shù)來關(guān)聯(lián)自定義數(shù)據(jù)到調(diào)度源??梢允褂蒙舷挛闹羔榿泶鎯κ录幚碚咛幚硎录枰娜魏螖?shù)據(jù)。如果確實在上下文指針中存儲了自定義數(shù)據(jù),應(yīng)當設(shè)置取消處理者(如設(shè)置取消處理者中描述)在調(diào)度源不再需要的時候釋放數(shù)據(jù)。
如果使用塊實現(xiàn)事件處理者,可以獲取本地變量并且在基于塊的代碼內(nèi)部使用它們。雖然這可能減少在調(diào)度源的上下文指針中存儲數(shù)據(jù)的需要,但要謹慎使用這個特性。因為調(diào)度源可能長期存活在應(yīng)用程序中,當獲取的變量包含指針時需要小心。如果數(shù)據(jù)被一個隨時可能釋放的指針指向,應(yīng)當復(fù)制數(shù)據(jù)或者保留它來防止發(fā)生這種情況。在任何情況下,都需要提供取消處理,以便稍后釋放這些數(shù)據(jù)。
調(diào)度源內(nèi)存管理
和其他調(diào)度對象一樣,調(diào)度源是引用計數(shù)數(shù)據(jù)類型。調(diào)度源有一個初始的引用計數(shù)1,可以使用dispatch_retain和dispatch_release函數(shù)來保留和釋放。當隊列的引用計數(shù)為0時,系統(tǒng)自動釋放調(diào)度源數(shù)據(jù)結(jié)構(gòu)。
根據(jù)使用的方式,可以在調(diào)度源自身的內(nèi)部或外部管理調(diào)度源的所有權(quán)。使用外部所有權(quán),另外一個對象或者代碼片段擁有調(diào)度源的所有權(quán),且負責在不再需要的時候釋放它。使用內(nèi)部所有權(quán),調(diào)度源擁有它自己,且負責在適當?shù)臅r候釋放自己。雖然內(nèi)部所有權(quán)是非常常見的,但可能會使用內(nèi)部所有權(quán)創(chuàng)建自動調(diào)度源,并讓它管理代碼的某些行為,而無需進一步的交互。例如,如果調(diào)度源被設(shè)計用來響應(yīng)單個全局事件,可能讓他處理事件然后立刻退出。
調(diào)度源示例
下面章節(jié)介紹如何創(chuàng)建和配置一些更常見的調(diào)度源的使用。更多關(guān)于配置指定類型調(diào)度源的信息,參閱GCD參考。
創(chuàng)建定時器
定時器調(diào)度源按照固定的間間隔生成事件??梢允褂枚〞r器啟動需要定期執(zhí)行的特殊任務(wù)。例如,游戲和其他圖形密集型應(yīng)用程序可能使用計時器來啟動屏幕或者更新動畫。也可以設(shè)置定時器,使用生成的事件來檢查服務(wù)器上頻繁更新的新信息。
所有定時器調(diào)度源都是間隔定時器,一旦創(chuàng)建,它以指定的間隔定期傳遞事件。當創(chuàng)建定時器調(diào)度源時,必須指定的一個值是余留值,以便系統(tǒng)了解定時器事件的所需精度。余留值給系統(tǒng)在電源管理和喚醒內(nèi)核方面一些靈活性。例如,系統(tǒng)可能使用余留值來提前或者延遲啟動時間,使其與其他系統(tǒng)事件更好的匹配。因此,應(yīng)該盡可能為自己的計時器指定一個余留值。
注意:即使指定余留值為0,也不要期待計時器在你要求的確切納秒啟動。 系統(tǒng)盡力滿足需求,但不能保證準確的啟動時間。
當計算機進入休眠狀態(tài)時,所有定時器調(diào)度源都將被掛起。當計算機喚醒時,這些定時器調(diào)度源也自動喚醒。根據(jù)定時器的配置,這種性質(zhì)的暫??赡苡绊懚〞r器下次啟動。如果使用dispatch_time函數(shù)或者DISPATCH_TIME_NOW常量設(shè)置定時器調(diào)度源,定時器調(diào)度源使用默認的系統(tǒng)時鐘來決定何時啟動。但是,在計算機處于休眠狀態(tài)時,默認時鐘不會提前。相反,當使用dispatch_walltime函數(shù)設(shè)置定時器調(diào)度源時,定時器調(diào)度源跟蹤啟動時間的掛鐘時間。后一選項通常用于觸發(fā)間隔相對較大的定時器,因為它防止在事件時間之間有太多誤差。
下面代碼是一個定時器例子,每30秒啟動一次,且具有1秒的余留值。因為計時器間隔比較大,使用dispatch_walltime函數(shù)創(chuàng)建調(diào)度源。定時器第一次啟動立即發(fā)生,隨后的事件每30秒到達一次。MyPeriodicTask和MyStoreTimer代表自定義函數(shù),用來實現(xiàn)定時器行為,并在應(yīng)用程序的數(shù)據(jù)結(jié)構(gòu)的一些地方保存定時器。
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);
}
}
雖然創(chuàng)建一個定時器調(diào)度源是接收基于時間事件的主要方法,但也有其他選項可用。如果想在指定的間隔后執(zhí)行一個塊,可以使用dispatch_after或dispatch_after_f函數(shù)。這個函數(shù)的行為很像dispatch_async函數(shù),只不過允許指定一個時間值,在這個時間值提交塊到隊列。這個時間值可以根據(jù)需要被指定為相對或者絕對的時間值。
從描述符讀取數(shù)據(jù)
為了從文件或套接字讀取數(shù)據(jù),必須打開文件或套接字,并創(chuàng)建DISPATCH_SOURCE_TYPE_READ類型的調(diào)度源。指定的事件處理者應(yīng)當能夠讀取和處理文件描述符的內(nèi)容。對于文件,這相當于讀取文件數(shù)據(jù)(或數(shù)據(jù)的子集)并創(chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。對于網(wǎng)絡(luò)套接字,這需要處理最新接受到的網(wǎng)絡(luò)數(shù)據(jù)。
無論什么時候讀取數(shù)據(jù),都應(yīng)該配置描述符使用非阻塞操作。雖然可以使用dispatch_source_get_data函數(shù)來看看有多少數(shù)據(jù)可以被讀取,但這個函數(shù)返回的值可以在調(diào)用這個函數(shù)到實際讀取數(shù)據(jù)之間被修改。如果底層文件被截斷或者網(wǎng)絡(luò)發(fā)生錯誤,從阻塞當前線程的描述符讀取,可能在事件處理者執(zhí)行中停止事件處理者,且阻止調(diào)度隊列調(diào)度其他任務(wù)。對于串行隊列,這可能死鎖隊列,即使對于并發(fā)隊列,這也會減少可以開始的新任務(wù)個數(shù)。
下面代碼是一個配置調(diào)度源從文件中讀取數(shù)據(jù)的示例。在這個例子中,事件處理者讀取指定文件的全部內(nèi)容到緩沖區(qū),并調(diào)用一個自定義函數(shù)(在自己代碼中定義)處理數(shù)據(jù)。(一旦讀取結(jié)束,這個函數(shù)的調(diào)用者可能使用返回的調(diào)度源取消它。)為了確保沒有數(shù)據(jù)讀取時調(diào)度隊列不會被不必要的阻塞,這個例子使用fcntl函數(shù)來配置文件描述符執(zhí)行非阻塞操作。取消處理者被設(shè)置到調(diào)度源上,確保文件描述符在讀取數(shù)據(jù)后關(guān)閉。
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函數(shù)決定何時讀取足夠的文件數(shù)據(jù)且調(diào)度源可以被取消。默認情況下,調(diào)度源被配置用來從描述符讀取數(shù)據(jù),當仍然有數(shù)據(jù)需要讀取時,重復(fù)調(diào)度事件處理者。如果套接字連接關(guān)閉或到達文件的結(jié)尾,調(diào)度源自動停止調(diào)度事件處理者。如果知道不再需要調(diào)度源,可以直接自己取消。
向描述符寫入數(shù)據(jù)
寫入數(shù)據(jù)到文件或套接字的處理和讀取數(shù)據(jù)的處理非常相似。為寫入操作配置描述符后,創(chuàng)建DISPATCH_SOURCE_TYPE_WRITE類型的調(diào)度源。一旦調(diào)度源被創(chuàng)建,系統(tǒng)調(diào)用事件處理者來給它一個機會開始寫入數(shù)據(jù)到文件或套接字。當結(jié)束寫入數(shù)據(jù)時,使用dispatch_source_cancel函數(shù)來取消調(diào)度源。
無論什么時候?qū)懭霐?shù)據(jù),都應(yīng)該配置描述符使用非阻塞操作。雖然可以使用dispatch_source_get_data函數(shù)來看看有多少空間可以被寫入,但這個函數(shù)返回的值只是建議性的,且可以在調(diào)用這個函數(shù)到實際寫入數(shù)據(jù)之間被修改。如果發(fā)生錯誤,向一個阻塞文件描述符寫入數(shù)據(jù),可能在事件處理者執(zhí)行中停止事件處理者,且阻止調(diào)度隊列調(diào)度其他任務(wù)。對于串行隊列,這可能死鎖隊列,即使對于并發(fā)隊列,這也會減少可以開始的新任務(wù)個數(shù)。
下面代碼展示使用調(diào)度源向文件寫入數(shù)據(jù)的基本方法。創(chuàng)建新的文件后,這個函數(shù)傳遞結(jié)果文件描述符給事件處理者。放入文件的數(shù)據(jù)由MyGetData函數(shù)提供,可以使用任何需要的代碼來替換,來為文件生成數(shù)據(jù)。寫入數(shù)據(jù)到文件后,事件處理者取消調(diào)度源來阻止再次調(diào)用。調(diào)度源的擁有者然后負責釋放它。
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);
}
監(jiān)視文件系統(tǒng)對象
如果想要監(jiān)視文件系統(tǒng)對象變化,可以設(shè)置調(diào)度源的類型為DISPATCH_SOURCE_TYPE_VNODE。當文件刪除,寫入或重命名時可以使用這種類型的調(diào)度源接收通知。也可以使用它在文件特殊類型的元信息(例如其大小和連接計數(shù))改變時發(fā)出警告。
注意: 當調(diào)度源自身在處理事件時,為調(diào)度源指定的文件描述符必須保持打開狀態(tài)。
下面代碼展示一個例子,監(jiān)視文件名字修改,且發(fā)生時執(zhí)行一些自定義行為。(可以在例子中調(diào)用的MyUpdateFileName函數(shù)中提供實際的行為。)因為描述符專門為調(diào)度源打開,所以調(diào)度源包含一個取消處理者來關(guān)閉描述符。因為例子創(chuàng)建的文件描述符和底層文件系統(tǒng)對象相關(guān)聯(lián),所以可以使用相同的調(diào)度源來檢測任意數(shù)量的文件名更改。
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;
}
監(jiān)控信號
UNIX信號允許從其域之外操縱應(yīng)用程序。應(yīng)用程序可以接受許多不同類型的信號,從不可恢復(fù)的錯誤(例如非法指令)到關(guān)于重要信息的通知(例如當子線程退出)。傳統(tǒng)上,應(yīng)用程序使用sigaction函數(shù)來設(shè)置一個信號處理者函數(shù),信號到達時它盡快的同步執(zhí)行。如果只是想得到信號到達的通知,并且實際上不想處理信號,可以使用信號調(diào)度源異步處理信號。
信號調(diào)度源不是使用sigaction函數(shù)設(shè)置同步信號處理者的替換者。同步信號處理者實際上可以捕獲信號,且阻止它終止應(yīng)用程序。信號調(diào)度源允許您僅監(jiān)控信號的到達。此外,不能使用信號調(diào)度源來檢索所有類型的信號。特別是,不能使用它們監(jiān)控SIGILL, SIGBUS和SIGSEGV信號。
由于信號調(diào)度源在調(diào)度隊列上異步執(zhí)行,所以他們不會受到與同步信號處理者相同的限制。例如,可以從信號調(diào)度源的事件處理者中調(diào)用函數(shù)而沒有限制。這種增加靈活性的折中,事實上可能會增加信號到達到調(diào)度源事件處理者被調(diào)用之間的等待時間。
下面代碼顯示如何配置一個信號調(diào)度源來處理SIGHUP信號。調(diào)度源的事件處理者調(diào)用MyProcessSIGHUP函數(shù),在應(yīng)用程序中替換這段代碼來處理者信號。
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);
}
}
如果您正在開發(fā)自定義框架的代碼,使用信號調(diào)度源的一個好處就是您的代碼可以監(jiān)視獨立于任何鏈接到它的應(yīng)用程序的信號。信號調(diào)度源不會干擾其他調(diào)度源或應(yīng)用程序已經(jīng)設(shè)置的任何同步信號處理者。
有關(guān)實現(xiàn)同步信號處理者以及信號名稱列表的更多信息,請參閱 signal 手冊頁。
監(jiān)視進程
進程調(diào)度源允許監(jiān)視特定進程的行為并作出適當?shù)捻憫?yīng)。父進程可能使用這個類型的調(diào)度源來監(jiān)視它創(chuàng)建的任何子進程。例如,父進程可能使用它來監(jiān)視子進程的死亡。同樣的,一個子進程可以使用它監(jiān)視其父進程,并且如果父進程退出,他也退出。
下面代碼顯示設(shè)置一個調(diào)度源來監(jiān)視父進程的終止的步驟。當父進程死亡,調(diào)度源設(shè)置一些內(nèi)部狀態(tài)信息,讓子進程知道它應(yīng)該退出。(您自己的應(yīng)用程序需要實現(xiàn)MySetAppExitFlag函數(shù)來為終止設(shè)置適當?shù)臉俗R。)因為調(diào)度源自動運行,因此擁有自己,也在應(yīng)用程序預(yù)期關(guān)閉時取消和釋放自己。
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);
}
}
取消調(diào)度源
調(diào)度源保持活躍直到使用dispatch_source_cancel函數(shù)明確取消它們。取消調(diào)度源將停止傳遞新的事件且無法挽回。因此,通常取消調(diào)度源,然后立刻釋放它(ARC不需要),如下所示:
void RemoveDispatchSource(dispatch_source_t mySource)
{
dispatch_source_cancel(mySource);
dispatch_release(mySource);
}
調(diào)度源的取消是一個異步操作。雖然調(diào)用dispatch_source_cancel函數(shù)后不再處理新的事件,但已經(jīng)被調(diào)度源處理的事件繼續(xù)進行處理。在處理完最后的任何事件后,調(diào)度源執(zhí)行取消處理者(如果存在)。
取消處理者是釋放內(nèi)存或清理調(diào)度源分配的資源的機會。如果調(diào)度源使用描述符或Mach端口,必須提供清理者在取消發(fā)生時關(guān)閉描述符或銷毀端口。其他類型的調(diào)度源不需要取消處理者,但如果關(guān)聯(lián)任何內(nèi)存或者數(shù)據(jù)到調(diào)度源,還是應(yīng)該提供取消處理者。例如,如果在調(diào)度源的上下文指針中存儲數(shù)據(jù),應(yīng)當提供取消處理者。關(guān)于取消處理者的更多信息,請參閱設(shè)置取消處理者。
暫停與恢復(fù)調(diào)度源
可以使用dispatch_suspend和dispatch_resume方法臨時暫停和回復(fù)調(diào)度源事件派發(fā)。這些方法增加和減少調(diào)度對象的暫停計數(shù)。因此,在事件恢復(fù)派發(fā)前,必須調(diào)用匹配方法dispatch_resume來平衡每次dispatch_suspend方法的調(diào)用。
當暫停調(diào)度源時,發(fā)生的任何事件都會被累積,直到隊列恢復(fù)。當隊列恢復(fù)時,在派發(fā)之前事件被合并為一個事件,而不是派發(fā)所有事件。例如,如果監(jiān)視一個文件名稱的更改,事件的派發(fā)將只包括最后一個名字的改變。這種方式的合并事件,將阻止事件在隊列中的建立和當恢復(fù)工作時淹沒應(yīng)用程序。
參考: