使用dispatch_sync和dispatch_async的串行队列

时间:2017-04-28 08:20:27

标签: objective-c grand-central-dispatch

以下用于从主线程调用[self goToNext]的代码具有与用于< 1>的不同dispatch_xxxx不同的结果。和< 2>。

  1. dispatch_sync& dispatch_sync,结果是死锁;
  2. dispatch_async& dispatch_async,结果为NULL;
  3. dispatch_sync& dispatch_async,结果是死锁;
  4. dispatch_async& dispatch_sync,结果为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]);
        });
    }
    

    有人可以解释四个结果的原因吗?

2 个答案:

答案 0 :(得分:1)

首先:您应该(重新?)阅读GCD简介。在运行之前尝试一些选项是 no 选项 - 你显然做了什么 - ,因为这可能会在下一台机器上失败。

您创建一个串行队列。在串行队列中,一个块相继执行。这描述了下标彼此的关系,而不是调用者。

同步或异步订阅描述了块和订阅块的代码之间的关系。这描述了彼此之间关系的关系,而是与&#34;调用者的关系&#34;:完全不同的东西。

下一步:测试线程毫无意义。队列可以更改它使用的线程。这就是它们的用途。

致你的问:

如果您将一个块订阅到串行队列中的串行队列,则内部队列必须等待外部队列完成,因为它是一个串行队列。这就是串行队列的本质。如果您使用dispatch_sync()执行此操作,则调用者将等待,直到块完成。因此永远不会完成:死锁。我们的代码有一个简化版本:

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. 
   });
   …
});

内部块无法完成,因为它必须在外部块完成之前等待(串行队列)。外部块无法完成,因为它等待内部块完成(同步)。死锁:两人都在等待对方完成。

您想要完全不同的东西:您想要使用块的结果。只需在完成处理程序中传递处理结果的代码。然后,您可以使用完成处理程序中的结果立即返回:

- (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 );
  });
}

然后这样称呼:

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

输入Safari。

BTW:在代码中用注释标记点,而不是使用内联标记。否则,没有人可以复制并粘贴代码并让它运行。

答案 1 :(得分:0)

在处理给定的代码块之前,对dispatch_async的调用立即返回。因此,在异步示例中,localSomeString在初始化之前返回(NULL)。你可以通过引入一个完成块作为方法someString的参数来解决这个问题,你可以在你的代码块中调用它:

- (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);
        }
    });
}

dispatch_sync的调用将等待,直到处理完代码块,从而阻止调用它的线程。因此,如果您在当前使用的队列上调用dispatch_sync,则当前使用的队列将一直等到代码块完成,但代码块永远不会执行,因为它位于相同(当前正在等待)的队列中。