Difference Between Dispatch_Async and Dispatch_Sync on Serial Queue

Difference between dispatch_async and dispatch_sync on serial queue?

Yes. Using serial queue ensure the serial execution of tasks. The only difference is that dispatch_sync only return after the block is finished whereas dispatch_async return after it is added to the queue and may not finished.

for this code

dispatch_async(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_async(_serialQueue, ^{ printf("3"); });
printf("4");

It may print 2413 or 2143 or 1234 but 1 always before 3

for this code

dispatch_sync(_serialQueue, ^{ printf("1"); });
printf("2");
dispatch_sync(_serialQueue, ^{ printf("3"); });
printf("4");

it always print 1234


Note: For first code, it won't print 1324. Because printf("3") is dispatched after printf("2") is executed. And a task can only be executed after it is dispatched.


The execution time of the tasks doesn't change anything. This code always print 12

dispatch_async(_serialQueue, ^{ sleep(1000);printf("1"); });
dispatch_async(_serialQueue, ^{ printf("2"); });

What may happened is

  • Thread 1: dispatch_async a time consuming task (task 1) to serial queue
  • Thread 2: start executing task 1
  • Thread 1: dispatch_async another task (task 2) to serial queue
  • Thread 2: task 1 finished. start executing task 2
  • Thread 2: task 2 finished.

and you always see 12

dispatch_async vs. dispatch_sync using Serial Queues in Grand Central Dispatch

dispatch_async() means that the block is enqueued and dispatch_async()returns to enqueueing another task/block (possibly) prior to the block being executed.

With dispatch_sync(), the block is enqueued and the function will not continue enqueueing another task/block until the block is executed.

The blocks are still executed serially. You could execute 100 dispatch_async() calls, each with a block that sleeps for 100 seconds, and it'd be really fast. Follow that with a call to dispatch_sync() on the same serial queue and dispatch_sync() will return ~10,000 seconds later.


To put it more simply:

dispatch_async(serialQ, block1);
dispatch_async(serialQ, block2);
dispatch_sync(serialQ, block3);

block1 will be executed before block2 which will be executed before block3. That is the order guaranteed by the serial queue.

However, the calls to dispatch_async() may return before any of the blocks start executing. The dispatch_sync() will not return before all three blocks are executed!

Serial Queue with dispatch_sync and dispatch_async

First of all: You should (re-?)read an introduction to GCD. Trying some options until one runs is no option – what you obviously did –, because this can fail on the next machine.

You create a serial queue. In a serial queue one block is executed after each other. This describes the relation of subscripted blocks to each other, not to the caller.

Subscribing synchronously or asynchronously describes the relation between the block and the code subscribing the block. This does not describe the relationships of blocks to each other, but the relationship to the "caller": Something completely different.

Next: Testing for a thread is meaningless. A queue can change the thread it uses. That's what they are for.

To your Q:

If you subscribe a block to a serial queue inside a serial queue, the inner one has to wait for the outer one to be completed, because it is a serial queue. That's the nature of a serial queue. If you do that with dispatch_sync() the caller waits to, until the block is completed. Therefore it is never completed: Dead lock. Let's have a simplified version of your code:

dispatch_sync(self.serialQueue,  // the caller waits for the code to be completed
^{

dispatch_sync(self.serialQueue, // the outer block waits for the code to be completed
^{
… // this code runs, after the outer block is completed.
});

});

The inner block cannot complete, because it has to wait, before the outer block is completed (serial queue). The outer block cannot complete, because it waits for the inner block to be completed (sync). Dead lock: Both are waiting for the other to be completed.

You want something completely different: You want to use the result of a block. Simply pass the code dealing with the result in a completion handler. Then you can return immediately using the result in a completion handler:

- (void)someStringWithCompletionHandler:(void(^)(NSString *result))handler // No return type, but a completion handler
{
__block NSString *localSomeString;
dispatch_async(self.serialQueue,
^{
if ([NSThread isMainThread]) {
NSLog(@"main thread");
} else {
NSLog(@"background thread");
}
localSomeString = @"fuck you!";
handler( localSomeString );
});
}

And then call it this way:

- (void)goToNext
{
dispatch_async(self.serialQueue,
^{
[self someStringWithCompletionHandler:
^(NSSTring *result)
{
NSLog( @"%@", result );
}]);
});
}

Typed in Safari.

BTW: Mark points in code with comments, not with inline markers. Otherwise none can copy and paste the code and let it run.

Why we need dispatch_sync() on another queue instead of using the current queue/thread in iOS GCD

There could be many reasons why you would want to do this, but one common use case is to guard a critical section of code. Say you have multiple threads that want to update an array. Arrays aren't thread safe so this could lead to the array becoming corrupted.

By using a dispatch_sync on to a serial queue and updating the array inside the dispatched block you can ensure that only one thread updates the array at a time. You need a synchronous dispatch because you want the requesting thread to wait until the array has been updated before continuing.

For example, here is a simple queue class that uses a serial dispatch queue to ensure thread safety when the underlying array is updated:

class Queue<T> {

private var theQueue = [T]()

private var dispatchQ = dispatch_queue_create("queueQueue", DISPATCH_QUEUE_SERIAL);

func enqueue(object:T) {
dispatch_sync(self.dispatchQ) {
self.theQueue.append(object)
}
}

func dequeue() -> T? {
return self.dequeue(true)
}

func peek() -> T? {
return self.dequeue(false)
}

private func dequeue(remove: Bool) -> T? {
var returnObject: T?
dispatch_sync(self.dispatchQ) {
if !self.theQueue.isEmpty {
returnObject = self.theQueue.first
if (remove) {
self.theQueue.removeFirst()
}
}
}

return returnObject
}

func isEmpty() -> Bool {
return self.theQueue.isEmpty
}
}

Dispatch_barrier_async and serial queue in GCD, what're differences between them?

dispatch_barrier_[a]sync are meant to be used with a concurrent queue. They are also meant to be used along with calls to dispatch_[a]sync.

The common usage is the "multi-readers, one writer" pattern. You setup a concurrent queue. For "reader" blocks, you use dispatch_[a]sync. For "writer" blocks, you use dispatch_barrier_[a]sync.

This setup allows concurrent reading but only allows one writer at a time and no reading while the writing is happening.

Compare this with a serial queue where only one block at a time can ever happen.

What is the difference between dispatch_sync and dispatch_main?

You don't generally want to use dispatch_main(). It's for things other than regular applications (system daemons and such). It is, in fact, guaranteed to break your program if you call it in a regular app.

dispatch_sync runs a block on a queue and waits for it to finish, dispatch_async runs a block on a queue and doesn't wait for it to finish.

Serial queues run one block at a time, in order. Concurrent queues run multiple blocks at a time, and therefore aren't necessarily in order.

(edit)

Perhaps when you said dispatch_main() you were thinking of dispatch_get_main_queue()?

Forcing the order of execution using dispatch_sync

It feels like you're trying to take an existing completion-handler based API and shoe-horn it into a serial queue pattern. But serial queues only make sense when the dispatched blocks are, themselves, synchronous. (And don't confuse this with dispatch_sync ... we're talking about whether the task inside the block is asynchronous or not, not how you dispatched it to your queue.) But you're dealing with asynchronous methods being called from other asynchronous methods.

The basic pattern when dealing with methods that take completion handler blocks, is to eliminate the serial queue altogether (no GCD queue is needed or is useful when the tasks are already asynchronous) and just use completion handler blocks in your own methods, and make the last call in the most deeply nested block call your own completion handler.

For example:

- (void)foo:(void (^)())completion {
[fileObj setFileInfo:file completion:^(NSError *error) {
// do some other stuff
completion();
}];
}

- (void)bar:(void (^)())completion {
[fileObj getFileInfo:fileName completion:^(NSError *error) {
// do some other stuff
completion();
}];
}

And then you'd call it like so:

[self foo:^{
[self bar:^{
// do whatever you want when its all done
}];
}];

This means that it won't do any of the getFile stuff until the setFile stuff is done.

Having said this, I wonder what added value your setFile and getFile methods add above and beyond the FileObject classes' own methods.

But the key is that when dealing with asynchronous methods, you can't easily coordinate them with a simple serial GCD queue. If you want dispatch queue like behavior for tasks that are, themselves, asynchronous, then you'd generally consider using operation queues instead (or promises/futures or something like that).

what is the difference between dispatch_async_f and dispatch_async?

dispatch_async -

Submits a block for asynchronous execution on a dispatch queue and returns immediately.
This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked.

Declaration : void dispatch_async( dispatch_queue_t queue, dispatch_block_t block);

Params :
queue - the queue on which block is to be submitted & can’t be NULL.
block - block to be submitted to the target queue & can’t be NULL.

dispatch_async_f -

Submits a application defined block for async execution on a dispatch queue & returns immediately.
This function is the fundamental mechanism for submitting application-defined functions to a dispatch queue. Calls to this function always return immediately after the function has been submitted and never wait for it to be invoked.

Declaration : void dispatch_async_f( dispatch_queue_t queue, void *context, dispatch_function_t work);

Params :
queue - the queue on which block is to be submitted & can’t be NULL.
work - application defined function to be invoked on target dispatch queue 7 can’t be NULL.

Understanding dispatch_queues and synchronous/asynchronous dispatch

This feels like an overly complicated way of thinking of it and there are lots of little details of that description that aren't quite right. Specifically, "it happens immediately on the current thread" is not correct.

First, let's step back: The distinction between dispatch_async and dispatch_sync is merely whether the current thread waits for it or not. But when you dispatch something to a serial queue, you should always imagine that it's running on a separate worker thread of GCD's own choosing. Yes, as an optimization, sometimes dispatch_sync will use the current thread, but you are in no way guaranteed of this fact.

Second, when you discuss dispatch_sync, you say something about it running "immediately". But it's by no means assured to be immediate. If a thread does dispatch_sync to some serial queue, then that thread will block until (a) any block currently running on on that serial queue finish; (b) any other queued blocks for that serial queue run and complete; and (c) obviously, the block that thread A itself dispatched runs and completes.

Now, when you use a serial queue for synchronization for, some thread-safe access to some object in memory, often that synchronization process is very quick, so the waiting thread will generally be blocked for a negligible amount of time as its dispatched block (and any prior dispatched blocks) to finish. But in general, it's misleading to say that it will run immediately. (If it always could run immediately, then you wouldn't need a queue to synchronize access).


Now your question talks about a "critical region", to which I assume you're talking about some bit of code that, in order to ensure thread-safety or for some other reason like that, must be synchronized. So, when running this code to be synchronized, the only question re dispatch_sync vs dispatch_async is whether the current thread must wait. A common pattern, for example, is to say that one may dispatch_async writes to some model (because there's no need to wait for the model to update before proceeding), but dispatch_sync reads from some model (because you obviously don't want to proceed until the read value is returned).

A further optimization of that sync/async pattern is the reader-writer pattern, where concurrent reads are permissible but concurrent writes are not. Thus, you'll use a concurrent queue, dispatch_barrier_async the writes (achieving serial-like behavior for the writes), but dispatch_sync the reads (enjoying concurrent performance with respect to other read operations).

dispatch_Async serial queue

Although the interleaving of the operations may not be known, you can see that there are two streams. The first is logging from a serial queue, the other is just plain NSLog.

What you can say about the order is that 1 will precede 3 and 2 will precede 4



Related Topics



Leave a reply



Submit