user3894894
user3894894

Reputation: 3

Serial Queue with dispatch_sync and dispatch_async

The following code to call [self goToNext] from main thread have different result with different dispatch_xxxx for <1> and <2>.

  1. dispatch_sync & dispatch_sync, the result is deadlock;
  2. dispatch_async & dispatch_async, the result is NULL;
  3. dispatch_sync & dispatch_async, the result is deadlock;
  4. dispatch_async & dispatch_sync, the result is NULL.

    - (NSString *)someString {
        __block NSString *localSomeString;
        dispatch_async(self.serialQueue, ^{     // dispatch_xxx <1>
            if ([NSThread isMainThread]) {
                NSLog(@"main thread");
            } else {
                NSLog(@"background thread");
            }
            localSomeString = @"fuck you!";
        });
        return localSomeString;
    }
    
    - (void)goToNext
    {
        dispatch_sync(self.serialQueue, ^{      // dispatch_xxx <2>
            if ([NSThread isMainThread]) {
                NSLog(@"main thread");
            } else {
                NSLog(@"background thread");
            }
            NSLog(@"%@", [self someString]);
        });
    }
    

    Could someone explain the reason of the four results?

Upvotes: 0

Views: 545

Answers (2)

Amin Negm-Awad
Amin Negm-Awad

Reputation: 16650

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.

Upvotes: 1

shallowThought
shallowThought

Reputation: 19602

A call to dispatch_async returns immediately, before processing the given block of code. Thus in your async example, localSomeString is returned before it is initialized (NULL). You can fix this by introducing a completion block as parameter to method someString which you than call within your block of code:

- (void)someStringWithCompletion:(void(^)(NSString* someString))completion {
    dispatch_sync<2>(self.serialQueue, ^{
        if ([NSThread isMainThread]) {    
            NSLog(@"main thread");
        } else {
            NSLog(@"background thread");
        }
        localSomeString = @"fuck you to!";
        completion(localSomeString)
    });
}

- (void)goToNext
{
    dispatch_sync<2>(self.serialQueue, ^{
        if ([NSThread isMainThread]) {
            NSLog(@"main thread");
        } else {
            NSLog(@"background thread");
        }
        [self someStringWithCompletion:^(NSString *result) {
            NSLog(result);
        }
    });
}

A call to dispatch_sync waits until the block of code is processed and thus blocks the thread it is called from. So if you are calling dispatch_sync on the currently used queue, the currently used queue is waiting until the block of code has finished, but the block of code is never executed as it is on the same (currently waiting) queue.

Upvotes: 0

Related Questions