[OC | GCD] dispatch_group_notify can not notify dispatch_get_main_queue (scheduling group can not notify the main queue) problem

The problem that the scheduling group cannot notify the main queue

written in front

通过dispatch_group来解决依赖问题是个经典的方案。
通常会在dispatch_group_notify中去执行更新UI的操作。
而更新UI操作必须放在主队列中。

今天本来想给同事演示一下以上的用法,给他打完下面这一段发了过去,
过了一会同事过来弱弱的说好像哪里不对劲,
我说我赌上我500度的眼镜片,肯定是你哪里敲错了,
感觉他抱着必死的决心说一个字一个字敲的,真的不对,
我还就不信这个邪,跟着他到他工位上一探究竟。

Scenario recurrence

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("cQueue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"开始");
    dispatch_group_async(group, queue, ^{
    
    
        NSLog(@"任务一");
    });
    
    dispatch_group_async(group, queue, ^{
    
    
        NSLog(@"任务二");
    });
    
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    
    
        NSLog(@"任务已全部完成");
    });
    
    NSLog(@"已阻塞");
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"解开阻塞");

The above is the code I gave him, first think about what will this code output?
Everyone will blurt out:

开始
已阻塞
任务一
任务二
解开阻塞
任务已全部完成

However the real output is this:

2022-06-18 11:38:29.246415+0800 多线程[23769:10338520] 开始
2022-06-18 11:38:29.246991+0800 多线程[23769:10338520] 已阻塞
2022-06-18 11:38:29.247007+0800 多线程[23769:10338756] 任务一
2022-06-18 11:38:29.247027+0800 多线程[23769:10338757] 任务二
2022-06-18 11:38:29.247064+0800 多线程[23769:10338520] 解开阻塞
Program ended with exit code: 0

What? Why is one missing, what about the output in dispatch_group_nofity? Pretend to be calm first, and try to see if you can get the correct result. If the main thread is not enough, then the sub-threads will come together. Change the
dispatch_group_notify to a sub-queue to see:

dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
    
    
        NSLog(@"任务已全部完成");
    });

run again

2022-06-18 13:25:11.230595+0800 多线程[29831:10437617] 开始
2022-06-18 13:25:11.231078+0800 多线程[29831:10437617] 已阻塞
2022-06-18 13:25:11.231104+0800 多线程[29831:10437935] 任务一
2022-06-18 13:25:11.231111+0800 多线程[29831:10437936] 任务二
2022-06-18 13:25:11.231146+0800 多线程[29831:10437936] 任务已全部完成
2022-06-18 13:25:11.231148+0800 多线程[29831:10437617] 解开阻塞
Program ended with exit code: 0

Woah, it's coming, it's coming, notify is coming with a sub-thread. When I saw this, the first thing I thought of was deadlock, but I immediately denied it. This has nothing to do with deadlock, so what could be the problem? I quickly went through the difference between the main thread and the child thread in my mind:

1.主线程只有一条,子线程可以有N条。
2.UI和main函数只能在主线程运行。
3.主线程的生命周期是贯穿整个程序,子线程可以自由开关。
4.主线程的runloop一开始就打开,子线程的runloop需要自己打开。

Wait, runloop? This is something similar to the while endless loop, which can grasp the life cycle of the entire program, and then look back at the output just now, including a sentence:

Program ended with exit code: 0

Digging lost, why did the program exit? Taking a closer look, it turns out that my colleague used the code run by the terminal program. This is a breakthrough point. Then think about the key to the continued existence of the UI program:

int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
    
        // Setup code that might create autoreleased objects goes here.
    }
    //这一句可以在程序启动时,自动启动主线程的runloop
    return NSApplicationMain(argc, argv);
}

solve

So the terminal program does not start the runloop of the main thread by default, but I obviously stuffed the task into the main queue, why not wait for the main queue to finish executing before ending the program?
First, with great doubts, turn on the runloop of the main thread and try to simulate the UI program:

int main(int argc, const char * argv[]) {
    
    
    [GCD_Demo main];   
    [[NSRunLoop currentRunLoop] run];
    return 0;
}
2022-06-18 13:48:42.107653+0800 多线程[31563:10471756] 开始
2022-06-18 13:48:42.108093+0800 多线程[31563:10471756] 已阻塞
2022-06-18 13:48:42.108122+0800 多线程[31563:10472055] 任务一
2022-06-18 13:48:42.108150+0800 多线程[31563:10472056] 任务二
2022-06-18 13:48:42.108216+0800 多线程[31563:10471756] 解开阻塞
2022-06-18 13:48:42.108307+0800 多线程[31563:10471756] 任务已全部完成

Sure enough, the desired effect is achieved. The information in dispatch_group_nofity is printed out. I feel that I am getting closer to the answer. It seems that it has something to do with the runloop. In this case, let’s check the print status of the runloop first to see if I can find it. There are clues:

+ (void)observer {
    
    
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, YES, 0, observeRunLoopActivities, NULL);
    // 添加observer到Runloop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);
}

void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    
    
    switch (activity) {
    
    
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            NSLog(@"default");
            break;
    }
}

Run it again:
runloopObserver
Yo Ho, what did you find? That's right, AfterWaiting, this is a signal to restart after the runloop sleeps, indicating that this is already the second round of the runloop.

Conclusion:
Adding the main queue to dispatch_group_nofity is to add the task to the next runloop of the main queue, and the terminal program is a one-time program, so it cannot wait for the second execution of the main thread, just like I I couldn't hear the cicadas that summer that year .

expand

So far, this question has been concluded, but we can't help thinking that the main queue dispatch_get_main_queue() can be used not only in dispatch_group_notify, but also in dispatch_async and dispatch_sync. Is the conclusion still applicable?
Not much to say, code verification:

    dispatch_async(dispatch_get_main_queue(), ^{
    
    
        NSLog(@"dispatch_async");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    
        dispatch_sync(dispatch_get_main_queue(), ^{
    
    
            NSLog(@"dispatch_sync");
        });
    });

Don't ask why dispatch_sync is nested one level. . Asking is a deadlock -. —!

2022-06-18 14:11:48.154426+0800 多线程[33514:10504920] kCFRunLoopBeforeSources
2022-06-18 14:11:48.154835+0800 多线程[33514:10504920] kCFRunLoopAfterWaiting
2022-06-18 14:11:48.154855+0800 多线程[33514:10504920] dispatch_async
2022-06-18 14:11:48.154868+0800 多线程[33514:10504920] dispatch_sync
2022-06-18 14:11:48.154882+0800 多线程[33514:10504920] kCFRunLoopBeforeSources

The result is obvious, and it is also added to the next cycle of runloop, so the above conclusion is also applicable,

in conclusion

主动往主队列中添加任务,这些任务会在下一次runloop中执行。

Guess you like

Origin blog.csdn.net/qq_41749924/article/details/125345066