Reputation: 3
The following code to call [self goToNext] from main thread have different result with different dispatch_xxxx for <1> and <2>.
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
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
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