Why can't we use a dispatch_sync on the current queue?

前端 未结 6 698
梦如初夏
梦如初夏 2020-11-28 20:51

I ran into a scenario where I had a delegate callback which could occur on either the main thread or another thread, and I wouldn\'t know which until runtime (using St

6条回答
  •  时光说笑
    2020-11-28 21:07

    dispatch_sync does two things:

    1. queue a block
    2. blocks the current thread until the block has finished running

    Given that the main thread is a serial queue (which means it uses only one thread), the following statement:

    dispatch_sync(dispatch_get_main_queue(), ^(){/*...*/});
    

    will cause the following events:

    1. dispatch_sync queues the block in the main queue.
    2. dispatch_sync blocks the thread of the main queue until the block finishes executing.
    3. dispatch_sync waits forever because the thread where the block is supposed to run is blocked.

    The key to understanding this is that dispatch_sync does not execute blocks, it only queues them. Execution will happen on a future iteration of the run loop.

    The following approach:

    if (queueA == dispatch_get_current_queue()){
        block();
    } else {
        dispatch_sync(queueA,block);
    }
    

    is perfectly fine, but be aware that it won't protect you from complex scenarios involving a hierarchy of queues. In such case, the current queue may be different than a previously blocked queue where you are trying to send your block. Example:

    dispatch_sync(queueA, ^{
        dispatch_sync(queueB, ^{
            // dispatch_get_current_queue() is B, but A is blocked, 
            // so a dispatch_sync(A,b) will deadlock.
            dispatch_sync(queueA, ^{
                // some task
            });
        });
    });
    

    For complex cases, read/write key-value data in the dispatch queue:

    dispatch_queue_t workerQ = dispatch_queue_create("com.meh.sometask", NULL);
    dispatch_queue_t funnelQ = dispatch_queue_create("com.meh.funnel", NULL);
    dispatch_set_target_queue(workerQ,funnelQ);
    
    static int kKey;
    
    // saves string "funnel" in funnelQ
    CFStringRef tag = CFSTR("funnel");
    dispatch_queue_set_specific(funnelQ, 
                                &kKey,
                                (void*)tag,
                                (dispatch_function_t)CFRelease);
    
    dispatch_sync(workerQ, ^{
        // is funnelQ in the hierarchy of workerQ?
        CFStringRef tag = dispatch_get_specific(&kKey);
        if (tag){
            dispatch_sync(funnelQ, ^{
                // some task
            });
        } else {
            // some task
        }
    });
    

    Explanation:

    • I create a workerQ queue that points to a funnelQ queue. In real code this is useful if you have several “worker” queues and you want to resume/suspend all at once (which is achieved by resuming/updating their target funnelQ queue).
    • I may funnel my worker queues at any point in time, so to know if they are funneled or not, I tag funnelQ with the word "funnel".
    • Down the road I dispatch_sync something to workerQ, and for whatever reason I want to dispatch_sync to funnelQ, but avoiding a dispatch_sync to the current queue, so I check for the tag and act accordingly. Because the get walks up the hierarchy, the value won't be found in workerQ but it will be found in funnelQ. This is a way of finding out if any queue in the hierarchy is the one where we stored the value. And therefore, to prevent a dispatch_sync to the current queue.

    If you are wondering about the functions that read/write context data, there are three:

    • dispatch_queue_set_specific: Write to a queue.
    • dispatch_queue_get_specific: Read from a queue.
    • dispatch_get_specific: Convenience function to read from the current queue.

    The key is compared by pointer, and never dereferenced. The last parameter in the setter is a destructor to release the key.

    If you are wondering about “pointing one queue to another”, it means exactly that. For example, I can point a queue A to the main queue, and it will cause all blocks in the queue A to run in the main queue (usually this is done for UI updates).

提交回复
热议问题