Dispatch Sources

1、簡介網絡

Dispatch Sources經常使用於處理跟系統有關的事件,協調處理指定的低級別的系統事件。在配置Dispatch Source時,需指定監控的事件類型、Dispatch Queues、Event Handle(blocks/functions)。當被監控的事件發生時,Dispatch Source提交Event Handle到指定的Dispatch Queues。app

不一樣於手動提交到queue中的任務,dispatch sources給應用提供了持續的事件資源。dispatch source除了明確取消,不然會持續與dispatch queue相關聯。無論何時指定的事件發生時,就會提交任務到關聯着的dispatch queue中。例如,定時器事件週期性的發生,還有大多數只有在指定條件下才發生的事件。爲此,dispatch sources持有關聯的dispatch queue,避免事件仍然會發生而dispatch queue被釋放了。異步

爲了不event handle被積壓在某個dispatch queue中,dispatch sources實現事件合併方案。若是前一個任務已出列並在處理時,新的事件到來了,dispatch source合併新事件和舊事件的數據。合併規則取決於事件的類型,合併可能代替舊事件,或者更新舊事件的數據。例如,基於信號的dispatch source會提供最近相關的信息,但也報告自從上次事件處理髮生以來總共發出了多少信號量。socket

Dispatch Sources包括這幾類:Timer dispatch sources、Signal dispatch sources、Descriptor sources、Process dispatch sources、Mach port dispatch sources和Custom dispatch sources。async

一、Timer dispatch sources週期性通知。
二、Signal dispatch sources爲unix信號發出時通知。
三、Descriptor sources各類各樣的file-和socket-操做通知。如從文件或者網絡中讀/寫數據,或文件名被重命名,或文件被刪、被移動、數據內容改動時。
四、Process dispatch sources父子process退出時等等操做通知。
五、Mach port dispatch sources
六、Custom dispatch sources函數

 

 

2、建立Dispatch Sources動畫

dispatch_source_create函數返回的是出於暫停狀態的dispatch source,在暫停狀態時,dispatch source接收通知但並不執行event handle。ui

一、Event Handlespa

event handle用於處理dispatch source的通知,經過dispatch_source_set_event_handle函數,爲dispatch source建立function/block類型的event handle。當事件到來時,dispatch source提交event handle到指定的dispatch queue。unix

event handle爲處理即將到來的全部事件負責。假設上一個event handle已經在隊列中等待被執行,又有新的event handle請求添加到queue中,dispatch source會合並兩個事件。然而當event handel正在執行,dispatch source等待它執行結束後,再將event handle提交到queue中。

// 基於block的event handle沒有參數也沒有返回值。
void (^dispatch_block_t)(void)
 
// 基於function的event handle包括上下文指針和dispatch source對象,無返回值。
void (*dispatch_function_t)(void *)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
                                 myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
   // block從外部捕獲到source變量
   size_t estimated = dispatch_source_get_data(source);
 
   // Continue reading the descriptor...
});
dispatch_resume(source);

二、Cancellation Handle

Cancellation handle用於在dispatch source被釋放前清理dispatch source。在大多數dispatch source類型中它是選擇性被實現,除了descriptor/mach port dispatch source需經過cancellation handle去關閉descriptor和釋放mach port。

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

三、Target Queue

在建立dispatch source須要指定調度event handle/cancellation handle的dispatch queue。在指定以後,還能夠經過dispatch_set_target_queue函數修改關聯的dispatch queue。通常修改target queue是用於修改queue的優先級,該操做是異步操做。所以在作修改操做前,已在舊dispatch queue中的任務繼續被調度執行。若是剛好在修改過程當中,添加任務到queue,該queue多是舊queue,也多是新queue。

四、Custom Data

跟GCD同樣,dispatch source能夠經過dispatch_set_context關聯自定義數據,原理是經過context pointer存儲event handle中須要用到的數據。注意的是建立了context pointer,就必須經過cancellation handle最終釋放那些存儲的數據。

另外一種方案是經過event handle用block實現,雖然也能捕獲變量,但變量隨時可能被釋放。所以這種方案須要經過拷貝並持有數據防止變量被回收,最終再經過cancellation handle釋放該變量。

五、Memory Management

知足內存管理原則,能夠經過dispatch_retain/dispatch_release來控制。

 

3、Dispatch Source案例

一、Create a Timer

timer dispatch source是週期性的timers,類型爲DISPATCH_SOURCE_TYPE_TIMER,leeway值是設置的容差值,若是leeway爲0,系統也沒法保證在指定週期執行任務。它經常使用於遊戲等應用刷新頻幕和動畫。

當電腦進入休眠時,timer dispatch source也被暫停,電腦恢復時它恢復,暫停會影響timer下一次執行。若是經過dispatch_time建立的timer,時間爲相對時間,它會使用系統鬧鐘,系統鬧鐘在電腦休眠時不會轉動。但若是經過 dispatch_walltime建立的timer,時間爲絕對時間,它使用wall鬧鐘,經常使用於大的時間間隔。

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_walltime()函數
      dispatch_source_set_event_handler(timer, block);
      dispatch_resume(timer);
   }
   return timer;
}
 
void MyCreateTimer()
{
   // 每30秒執行一次,容差1秒,event handle中具體實現爲MyPeriodicTask()
   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);
    }
}

 除了timer dispatch source按期處理系統事件,還有dispatch_after在指定時間以後執行一次某事件,dispatch_after就像指定了時間的dispatch_async函數。

二、Reading Data from a Descriptor 

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); // 避免阻塞讀數據進程
 
   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;
   }
 
   // Event Handler
   dispatch_source_set_event_handler(readSource, ^{
    size_t estimated
= dispatch_source_get_data(readSource) + 1; // 讀取數據至buffer char* buffer = (char*)malloc(estimated); if (buffer){ ssize_t actual = read(fd, buffer, (estimated)); Boolean done = MyProcessFileData(buffer, actual); // 處理數據 free(buffer); // 讀取完畢,取消該source。 if (done) dispatch_source_cancel(readSource); } }); // Cancellation Handler dispatch_source_set_cancel_handler(readSource, ^{close(fd);}); // 開始讀文件 dispatch_resume(readSource);
return readSource; }  

三、Writing Data to a Descriptor

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

四、Monitoring a File-System Object

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

五、Monitoring Signals

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

六、Monitoring a Process

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

 

4、取消Dispatch Source

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

 

5、暫停和恢復Dispatch Source

相關文章
相關標籤/搜索