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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Managing Units of Work(管理工作单位) 调度块允许您直接配置队列中各个工作单元的属性。它们还...
    edison0428阅读 7,931评论 0 1
  • iOS多线程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst阅读 1,196评论 0 0
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 784评论 0 3
  • GCD(Grand Central Dispatch) 介绍 GCD 属于系统级的线程管理,在 Dispatch ...
    fuyoufang阅读 4,629评论 0 10
  • 常规分页查询缓存方案 我们都知道,通过缓存查询的结果,可以极大的提升系统的服务能力,以及降低底层服务或者是数据库的...
    OneFish阅读 35,021评论 6 10