dispatch_async vs dispatch_sync execution order
Asked Answered
M

2

9

I have a serial dispatch queue created with:

dispatch_queue_t serialQueue = dispatch_queue_create("com.unique.name.queue", DISPATCH_QUEUE_SERIAL);

I want to use this serial queue to ensure thread safety for class access, while automatically doing work asynchronously that doesn't need to return to the calling thread.

- (void)addObjectToQueue:(id)object
{
    dispatch_async(serialQueue, ^{
        // process object and add to queue
    }); 
}

- (BOOL)isObjectInQueue:(id)object
{
    __block BOOL returnValue = NO;
    dispatch_sync(serialQueue, ^{
        // work out return value
    });
    return returnValue;
} 

If I call the addObjectToQueue: method, then immediately call the isObjectInQueue: method, are they guaranteed to be executed in the same order, or will/could the isObjectInQueue execute first?

In other words, does dispatch_async perform exactly the same as dispatch_sync (scheduling the block immediately) except that it doesn't block the calling thread?

I have seen similar questions with answers going both ways, so I am looking for a definitive answer, preferably backed with Apple documentation.

Mashie answered 12/1, 2013 at 11:47 Comment(1)
"If I call the addObjectToQueue: method, then immediately call the isObjectInQueue: method, are they guaranteed to be executed in the same order, or will/could the isObjectInQueue execute first?" by asking this you probably mean "Could we ask for the object before it was added to queue?". Because, obviously, 2 methods run in order they are called if called on same thread.Fredelia
B
13

Are they guaranteed to be executed in the same order?

Yes.

Will / could the isObjectInQueue execute first?

Yes.

The reason for the yes to both answers is you should consider threading. Which is presumably why you are using the serial queue in the first place. You are making access to that queue thread safe.

Basically, the blocks will execute in the order in which they are put on the serial queue. That is 100% guaranteed. However, if multiple threads are hammering away at this then one thread may get in first to read something from the queue before another has had chance to add it.

In other words, does dispatch_async perform exactly the same as dispatch_sync (scheduling the block immediately) except that it doesn't block the calling thread?

That's right. In both cases the block is added to the queue. It is added immediately. dispatch_sync just waits for the block to finish before returning whereas dispatch_async returns immediately.

Breeching answered 12/1, 2013 at 12:49 Comment(2)
Right; or, to summate, blocks on a serial queue will be executed in the order added and that order will only be deterministic when you dispatch_async()/dispatch_sync() from a single thread. If multi-threaded and you need order, you'll have to provide a synchronization mechanism. You'll probably need a "freshness" indicator for the return value of isObjectInQueue: as that may likely lose validity quickly in a concurrent environment. I.e. concurrency is hard.Cairo
Although this discussion is old I'd like to revive it to clarify some moments. 1. "Will / could the isObjectInQueue execute first? Yes." Andrew asks if METHODS could run in random order being called in one after another, not blocks inside the dispatch_async. but: 2. He probably means blocks himself. :). But still, can one block be executed before the other one if added last on serial queue? "dispathc_async - Submits a block for asynchronous execution on a dispatch queue and returns immediately. Calls to this function always return immediately after the block has been submitted."Fredelia
F
2

I guess your question is, will the main thread keep running while dispatch_async is still executing the queue operation? I assume it won't, because that would deserve a explicit mention. If anything, I found this in dispatch_async.3 which suggests this is the case:

Conceptually, dispatch_sync() is a convenient wrapper around dispatch_async() with the addition of a semaphore to wait for completion of the block, and a wrapper around the block to signal its completion.

And indeed, if you follow the source code for dispatch_async in queue.c you'll see that the block is queued on the foreground, and only after that, execution returns to the code that called dispatch_async. Therefore if the queue is serial, dispatch_async followed by a dispatch_sync from the same thread will queue the blocks in order.

Because dispatch_sync will block until the block (and all blocks before in a serial queue) are done executing, then your code would be right. isObjectInQueue: will correctly report if the object added before is in the queue.

edit: on a multithreaded environment I would write the code above as:

- (void)addObjectToQueue:(id)object
{
    dispatch_barrier_async(_queue, ^{
        // process object and add to queue
    });
}

- (BOOL)isObjectInQueue:(id)object
{
    __block BOOL returnValue = NO;
    dispatch_sync(_queue, ^{
        // work out return value
    });
    return returnValue;
} 

because execution of each method can be deferred at any point in favor of another thread.

Formal answered 12/1, 2013 at 12:47 Comment(3)
From reading the dispatch_barrier_async documentation, if you are using a serial queue created yourself the behaviour will be the same as dispatch_async. Is that correct?Mashie
Yes. The code above assumes the use of a concurrent queue for a multithreaded environment because serializing reads would be a pointless roadblock. With a serial queue however, there are no blocks executing concurrently to apply the barrier to, so a dispatch_barrier_async is equivalent to a dispatch_async.Formal
dispatch_barrier_async() does nothing on a serial queue because, by definition, all prior enqueued blocks will be executed by the time the barrier'd block is executed. It only impacts concurrent queues.Cairo

© 2022 - 2024 — McMap. All rights reserved.