GCD 之任務操作(Dispatch Block)

在向隊列中添加任務時,可以直接在對應的函數中添加 block。但是如果想對任務進行操作,比如監聽任務、取消任務,就需要獲取對應的 block

創建block

  • object-c
    創建 block 有兩種方式,第一種方式如下:

    dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
    

    在該函數中,flags 參數用來設置 block 的標記,block 參數用來設置具體的任務。flags 的類型為 dispatch_block_flags_t 的枚舉,用于設置 block 的標記,定義如下:

    DISPATCH_ENUM(dispatch_block_flags, unsigned long,
        DISPATCH_BLOCK_BARRIER = 0x1,
        DISPATCH_BLOCK_DETACHED = 0x2,
        DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
        DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
        DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
        DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,
    );
    

    創建 block 的另一種方式如下:

    dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,
        dispatch_qos_class_t qos_class, int relative_priority,
        dispatch_block_t block);
    

    相比于 dispatch_block_create 函數,這種方式在創建 block 的同時可以指定了相應的優先級。dispatch_qos_class_tqos_class_t 的別名,定義如下:

    #if __has_include(<sys/qos.h>)
    typedef qos_class_t dispatch_qos_class_t;
    #else
    typedef unsigned int dispatch_qos_class_t;
    #endif
    

    qos_class_t 是一種枚舉,有以下類型:

    • QOS_CLASS_USER_INTERACTIVE:user interactive 等級表示任務需要被立即執行,用來在響應事件之后更新 UI,來提供好的用戶體驗。這個等級最好保持小規模。

    • QOS_CLASS_USER_INITIATED:user initiated 等級表示任務由 UI 發起異步執行。適用場景是需要及時結果同時又可以繼續交互的時候。

    • QOS_CLASS_DEFAULT:default 默認優先級

    • QOS_CLASS_UTILITY:utility 等級表示需要長時間運行的任務,伴有用戶可見進度指示器。經常會用來做計算,I/O,網絡,持續的數據填充等任務。這個任務節能。

    • QOS_CLASS_BACKGROUND:background 等級表示用戶不會察覺的任務,使用它來處理預加載,或者不需要用戶交互和對時間不敏感的任務。

    • QOS_CLASS_UNSPECIFIED:unspecified 未指明

    事例:

    dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"normal do some thing...");
    });
    dispatch_async(concurrentQuene, block);
    
    //
    dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_DEFAULT, 0, ^{
        NSLog(@"qos do some thing...");
    });
    dispatch_async(concurrentQuene, qosBlock);
    
  • swift 3.0
    swift 3.0 中使用了 DispatchWorkItem 對任務進行了封裝。可以在 DispatchWorkItem 初始化方法中指定任務的內容和優先級。事例如下:

    let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
    
    let block = DispatchWorkItem.init(block: {
        print("normal do some thing...")
    })
    concurrentQueue.async(execute: block);
    
    let qosBlock = DispatchWorkItem.init(qos: .default, flags: .noQoS, block: {
        print("qos do some thing...")
    })
    

監聽 block 執行結束

有時我們需要等待特定的 block 執行完成之后,再去執行其他任務。有兩種方法可以獲取到指定 block 執行結束的時機。

  • object-c
    object-c 中,第一種為使用下面的函數:

    long dispatch_block_wait(dispatch_block_t block, dispatch_time_t timeout);
    

    該函數會阻塞當前線程進行等待。傳入需要設置的 block 和等待時間 timeouttimeout 參數表示函數在等待 block 執行完畢時,應該等待多久。如果執行 block 所需的時間小于 timeout,則返回 0,否則返回非 0 值。此參數也可以取常量 DISPATCH_TIME_FOREVER,這表示函數會一直等待 block 執行完,而不會超時。可以使用 dispatch_time 函數和 DISPATCH_TIME_NOW 常量來方便的設置具體的超時時間。
    如果 block 執行完成,dispatch_block_wait 就會立即返回。不能使用 dispatch_block_wait 來等待同一個 block 的多次執行全部結束;這種情況可以考慮使用 dispatch_group_wait 來解決。也不能在多個線程中,同時等待同一個 block 的結束。同一個 block 只能執行一次,被等待一次。

    注意:因為 dispatch_block_wait 會阻塞當前線程,所以不應該放在主線程中調用。

    事例

    dispatch_queue_t concurrentQuene = dispatch_queue_create("concurrentQuene", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQuene, ^{
        dispatch_queue_t allTasksQueue = dispatch_queue_create("allTasksQueue", DISPATCH_QUEUE_CONCURRENT);
    
        dispatch_block_t block = dispatch_block_create(0, ^{
            NSLog(@"開始執行");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"結束執行");
        });
    
        dispatch_async(allTasksQueue, block);
        // 等待時長,10s 之后超時
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));
        long resutl = dispatch_block_wait(block, timeout);
        if (resutl == 0) {
            NSLog(@"執行成功");
        } else {
            NSLog(@"執行超時");
        }
    });
    

    輸入結果:

    開始執行
    結束執行
    執行成功

  • swift 3.0
    在 swift 3.0 中,只需調用 DispatchWorkItem 的實例方法即可等待任務結束,如下:

    public func wait()
    public func wait(timeout: DispatchTime) -> DispatchTimeoutResult
    public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
    

    事例:

    let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
    
    concurrentQueue.async(execute: {
        let allTesksQueue = DispatchQueue.init(label: "allTesksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
    
        let workItem = DispatchWorkItem.init(block: {
            print("開始執行")
            sleep(3)
            print("結束執行")
        })
    
        allTesksQueue.async(execute: workItem)
        
        // 設置超時時間,超時時間為 10s
        let timeout = DispatchTime.now() + 10
        let result = workItem.wait(timeout: timeout)
    
        if result == .success {
            print("執行成功")
        } else {
            print("執行超時")
        }
    })
    

輸入結果同 object-c

第二種監聽 block 執行完成的方法如下:

  • object-c
    在 object-c 中可用下面的函數:

    void dispatch_block_notify(dispatch_block_t block,     
            dispatch_queue_t queue, 
            dispatch_block_t notification_block);
    

    該函數接收三個參數,第一個參數是需要監視的 block,第二個參數是監聽的 block 執行結束之后要提交執行的隊列 queue,第三個參數是待加入到隊列中的 block。 和 dispatch_block_wait 的不同之處在于:dispatch_block_notify 函數不會阻塞當前線程。
    例如:

    NSLog(@"---- 開始設置任務 ----");
    dispatch_queue_t serialQueue =   dispatch_queue_create("com.fyf.serialqueue",   DISPATCH_QUEUE_SERIAL);
    
    // 耗時任務
    dispatch_block_t taskBlock = dispatch_block_create(0, ^{
        NSLog(@"開始耗時任務");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"完成耗時任務");
    });
    
    dispatch_async(serialQueue, taskBlock);
    
    // 更新 UI
    dispatch_block_t refreshUI = dispatch_block_create(0, ^{
        NSLog(@"更新 UI");
    });
    
    // 設置監聽
    dispatch_block_notify(taskBlock, dispatch_get_main_queue(), refreshUI);
    NSLog(@"---- 完成設置任務 ----");
    

    輸出:

    ---- 開始設置任務 ----
    ---- 完成設置任務 ----
    開始耗時任務
    完成耗時任務
    更新 UI

  • swift 3.0
    在 swift 3.0 中,可以使用 DispatchWorkItem 的實例方法進行設置,方法如下:

    public func notify(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, queue: DispatchQueue, execute: @escaping @convention(block) () -> Swift.Void)
    
    public func notify(queue: DispatchQueue, execute: DispatchWorkItem)
    

    事例:

    print("---- 開始設置任務 ----")
    
    let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil);
    
    let refreshUIWorkItem = DispatchWorkItem.init(block: {
        print("更新 UI")
    })
    
    let tasksWorkItem = DispatchWorkItem.init(block: {
        print("開始耗時任務")
        sleep(3)
        print("完成耗時任務")
    })
    
    tasksWorkItem.notify(queue: DispatchQueue.main, execute: refreshUIWorkItem)
    tasksQueue.async(execute: tasksWorkItem)
    
    print("---- 完成設置任務 ----")
    

    輸出:

    ---- 開始設置任務 ----
    ---- 完成設置任務 ----
    開始耗時任務
    完成耗時任務
    更新 UI

任務的取消

iOS8 后 GCD 支持對 dispatch block 的取消。方法如下:

  • object-c
    可以使用下面的函數取消 dispatch block:

    void dispatch_block_cancel(dispatch_block_t block);
    

    這個函數用異步的方式取消指定的 block。取消操作使將來執行 dispatch block 立即返回,但是對已經在執行的 dispatch block 沒有任何影響。當一個 block 被取消時,它會立即釋放捕獲的資源。如果要在一個 block 中對某些對象進行釋放操作,在取消這個 block 的時候,需要確保內存不會泄漏。

    例子:

    dispatch_queue_t serialQueue = dispatch_queue_create("com.fyf.serialqueue", DISPATCH_QUEUE_SERIAL);
    
    // 耗時任務
    dispatch_block_t firstTaskBlock = dispatch_block_create(0, ^{
        NSLog(@"開始第一個任務");
        [NSThread sleepForTimeInterval:1.5f];
        NSLog(@"結束第一個任務");
    });
    
    // 耗時任務
    dispatch_block_t secTaskBlock = dispatch_block_create(0, ^{
        NSLog(@"開始第二個任務");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"結束第二個任務");
    });
    
    dispatch_async(serialQueue, firstTaskBlock);
    dispatch_async(serialQueue, secTaskBlock);
    
    // 等待 1s,讓第一個任務開始運行
    [NSThread sleepForTimeInterval:1];
    
    dispatch_block_cancel(firstTaskBlock);
    NSLog(@"嘗試過取消第一個任務");
    
    dispatch_block_cancel(secTaskBlock);
    NSLog(@"嘗試過取消第二個任務");
    

    輸出:

    開始第一個任務
    嘗試過取消第一個任務
    嘗試過取消第二個任務
    結束第一個任務

    可見 dispatch_block_cancel 對已經在執行的任務不起作用,只能取消尚未執行的任務。

  • swift 3.0
    在 swift 3.0 中,可以使用 DispatchWorkItem 的實例方法進行設置,方法如下:

    public func cancel()
    

    事例:

    let tasksQueue = DispatchQueue.init(label: "com.fyf.tasksQueue");
    
    let firstWorkItem = DispatchWorkItem.init(block: {
        print("開始第一個任務")
        sleep(2)
        print("結束第一個任務")
    })
    
    let secondWorkItem = DispatchWorkItem.init(block: {
        print("開始第二個任務")
        sleep(2)
        print("結束第二個任務")
    })
    
    tasksQueue.async(execute: firstWorkItem)
    tasksQueue.async(execute: secondWorkItem)
    
    // 等待 1s,讓第一個任務開始運行
    sleep(1)
    
    firstWorkItem.cancel()
    print("嘗試過取消第一個任務")
    secondWorkItem.cancel()
    print("嘗試過取消第二個任務")
    

    輸出內容同 object-c

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Managing Units of Work(管理工作單位) 調度塊允許您直接配置隊列中各個工作單元的屬性。它們還...
    edison0428閱讀 8,032評論 0 1
  • iOS多線程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst閱讀 1,224評論 0 0
  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 853評論 0 3
  • GCD(Grand Central Dispatch) 介紹 GCD 屬于系統級的線程管理,在 Dispatch ...
    fuyoufang閱讀 4,736評論 0 10
  • 常規分頁查詢緩存方案 我們都知道,通過緩存查詢的結果,可以極大的提升系統的服務能力,以及降低底層服務或者是數據庫的...
    OneFish閱讀 35,099評論 6 10