美文网首页
Dispatch常用方法

Dispatch常用方法

作者: 哦小小树 | 来源:发表于2020-06-08 09:20 被阅读0次

    dispatch_set_target_queue

    1. 变更Dispatch Queue的执行优先级
    2. 创建队列层次体系
    queueA = dispatch_queue_create("com.lyk.queueA", NULL);
    queueB = dispatch_queue_create("com.lyk.queueB", DISPATCH_QUEUE_CONCURRENT);
    
    // 将queueB队列中的任务依次添加到queueA中。这样queueB的优先级就与queueA保持一致了。
    // 而且设置了target后,在判断当前任务执行队列会出现点事情
    dispatch_set_target_queue(queueB, queueA);
    

    延伸逻辑

    创建队列层次体系,是什么样的层次体系?先看一个例子

    // 准备工作
    static void * queueuBKey = &queueuBKey;
    
    queueA = dispatch_queue_create("com.lyk.queueA", NULL);
    queueB = dispatch_queue_create("com.lyk.queueB", DISPATCH_QUEUE_CONCURRENT);
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    //        dispatch_queue_set_specific(queueA, queueuAKey, queueuAKey, NULL);
        dispatch_queue_set_specific(queueB, queueuBKey, queueuBKey, NULL);
    });
    
    // 测试
    dispatch_set_target_queue(queueA, queueB);
    
    dispatch_sync(queueB, ^{
        NSLog(@"queueB-是否为B:%@", dispatch_get_specific(queueuBKey) == queueuBKey ? @"True" : @"False");
    });
    
    dispatch_sync(queueA, ^{
        NSLog(@"queueA-是否为B:%@", dispatch_get_specific(queueuBKey) == queueuBKey ? @"True" : @"False");
    });
    
    /* 打印输出
    * queueB-是否为B:True
    * queueA-是否为B:True
    */
    
    // 如果设置成这样
    dispatch_set_target_queue(queueB, queueA); 
    
    /* 打印输出
    * queueB-是否为B:True
    * queueA-是否为B:False
    */
    

    使用dispatch_get_specific来验证当前队列,我们看下其内部实现原理:

    void *  dispatch_get_specific(const void *key)
    {
        if (slowpath(!key)) {
            return NULL;
        }
    
        void *ctxt = NULL;
      
        dispatch_queue_t dq = _dispatch_queue_get_current();
      
        while (slowpath(dq)) {
            if (slowpath(dq->dq_specific_q)) {
                ctxt = (void *)key;
                dispatch_sync_f(dq->dq_specific_q, &ctxt,
                        _dispatch_queue_get_specific);
                if (ctxt) break;
            }
            dq = dq->do_targetq;    // 如果当前队列找不到标识,就会根据其targetq向上找
        }
        return ctxt;
    }
    

    通过上例可以发现,上述打印效果区分:

    // 相当于queueB的targetq是queueA
    dispatch_set_target_queue(queueB, queueA); 
    
    /*以下展示目标关系,B.do_targetq = A; A 在上游,B在下游
    A NULL          ; 当在A队列中执行以下代码获取到 NULL ,向上游找targetq结果还是找不到
    B   queueuBKey; 当在B队列中执行以下代码会获取到 queueuBKey
    */
    dispatch_get_specific(queueuBKey)       // queueA,判断不为B; queueB判断为B
    
    // 相当于queueA的targetq是queueB
    dispatch_set_target_queue(queueA, queueB); 
    
    /*以下展示目标关系,A.do_targetq = B; B 在上游,A在下游
    B   queueuBKey; 当在B队列中执行以下代码会获取到 queueuBKey
    A NULL          ; 当在A队列中获取为NULL,需要顺着targetq向上游找,就找到B,进而找到queueBKey
    */
    dispatch_get_specific(queueuBKey);      // queueA,QueueB都可以判断为B队列
    

    总结来说:dispatch_set_target_queue(queueA, queueB);会将queueA队列中的任务提交到queueB中执行

    所以在queueA中的任务在执行过程中通过dispatch_get_specific判断当前队列时判断为queueAqueueB队列。而queueB中的任务在执行中判断当前队列时只能判断为queueB

    查找当前队列,如果查找不到会顺着do_targetq向上游查找。


    dispatch_after

    3s后将任务块添加到MainDispatchQueue中;注意不是在指定时间后执行处理,只是在指定的时间追加操作到指定队列中。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"waited at least three seconds.");
    });
    

    dispatch_group

    用来创建任务组,当任务组执行完毕后再去执行其他任务

    常用方法:

    /*永久等待:第二个参数指定为等待的时间(超时),此处意味着永久等待。
    只要属于 Dispatch Group 的处理尚未执行结束,就会一直等待,中途不能取消*/
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    /*等待指定时间: 注意超时时间的入参*/
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC));
        NSLog(@"是否执行超时:%@",timeout == 0 ? @"没有超时":[NSString stringWithFormat:@"超时%.2fs",timeout/1000.0f]);
    
     /*用来监听队列中的任务已全部执行完毕,任务块将会放到queue队列中执行*/
     dispatch_group_notify(group, queue, ^{NSLog(@"done");});
    
    使用方式

    以下两种方式使用起来效果一致,但是如果block内部又涉及到异步队列操作问题,可能会碰到第一种方法解决不了的情况,此时就需要考虑使用第二种方式来保证顺序了。

    1. 通过调度组关联队列的方式,将一个block添加到队列中并且与一个组进行关联,当任务执行完毕
    void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
    
    2. 通过自己嵌入队列中,需要注意是手动关联队列,enter与leave必须要保持平衡;而且可以关联多个组
    dispatch_group_enter(group);
    dispatch_async(queue1, ^{
        NSLog(@"测试4,%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    block内部涉及队列导致group保证不了顺序的情况

    需要注意,当我们通过dispatch_group_async添加一个任务指的是将任务添加到队列,同时关联组,当执行完毕这个任务就通知调度组,它并去去管你这个队列内部做了什么操作(是去执行任务,还是将任务添加到其他队列)。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("con.song.serial", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        // dispatch_group_enter(group);             // 打开注释可以解决notify通知不到的问题
        dispatch_async(dispatch_get_global_queue(0, 0), ^{  
            // 执行一些任务块
            sleep(3);
            NSLog(@"测试1,%@",[NSThread currentThread]);
            // dispatch_group_leave(group);
        });
    });
    dispatch_group_async(group, queue, ^{
        // 执行一些任务块
        NSLog(@"测试2,%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"执行任务结束:%@",[NSThread currentThread]);
    });
    
    // 打印结果(notify不是最后打印的)
    QueueTest[51292:1686388] 测试2,<NSThread: 0x600003e8f980>{number = 6, name = (null)}
    QueueTest[51292:1686192] 执行任务结束:<NSThread: 0x600003ec4f00>{number = 1, name = main}
    QueueTest[51292:1686392] 测试1,<NSThread: 0x600003e8ed40>{number = 3, name = (null)}
    

    dispatch_barrier_async

    只能用自己创建的并行队列来操作

    添加栅栏操作后,会等待已经追加到队列中的block全部执行完毕。


    dispatch_sync

    同步执行任务,如果在串行队列中执行容易导致死锁问题,注意处理。


    dispatch_apply

    是dispatch_sync函数和dispatch_group的关联的API。该函数按照指定的次数将指定的block追加到指定的dispatch_queue中,病等待全部操作执行结束。

    dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(3, queue, ^(size_t index) {
        NSLog(@"%zu", index);
    });
    NSLog(@"done");
    

    dispatch_suspendispatch / dispatch_resume

    挂起指定的队列/恢复指定的队列。

    这些函数对已经执行的处理没有影响。

    挂起后,追加到Dispatch Queue中但尚未执行的操作在此之后停止执行。

    而恢复则使得这些处理能继续执行。


    dispatch_semaphore

    持有计数的信号,该计数是多线程中的计数类型信号。

    可以用来做任务同步,比如讲异步网络请求通过这种方案设计为同步请求操作。

    // 创建一个信号 v = 1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    // 执行一个任务会将信号量 -1; 注意如果信号量<1此时会进入等待,可以设置等待超时时间
    // 如果返回值为0说明正常操作,如果返回值不为0则代表超时时间
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    // 释放一个信号会将信号量 +1
    dispatch_semaphore_signal(semaphore);
    
    

    dispatch_once

    保证应用程序执行中只执行一次指定的逻辑

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 初始化操作
    });
    

    dispatchI/O

    适用场景:适合文章类的读取操作(PDF,md,work,txt),因为串行队列,所以读取的文章是顺序的,在实际中使用更多。


    参考:

    https://www.jianshu.com/p/33d6f52fe26b
    [https://raykle.github.io/2016/08/16/《iOS%20与%20OS%20X%20多线程和内存管理》读书笔记之%20GCD(二)/](iOS与OS X多线程和内存管理)/

    相关文章

      网友评论

          本文标题:Dispatch常用方法

          本文链接:https://www.haomeiwen.com/subject/aodnzhtx.html