dispatch_semaphore_wait()无法接收信号量

时间:2016-09-23 08:56:44

标签: ios grand-central-dispatch

我的代码是:

- (NSString*)run:(NSString*)command{
    _semaphore = dispatch_semaphore_create(0);

    // Create and start timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f
                                             target:self
                                           selector:@selector(getState:)
                                           userInfo:nil
                                            repeats:YES];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
    [runLoop run];

    //and it stuck there
    // Wait until signal is called
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    return _state;
}

- (void)getState:(NSTimer*)time{

    // Send the url-request.
    NSURLSessionDataTask* task =
    [_session dataTaskWithRequest:_request
               completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                   if (!error) {
                       NSLog(@"result: %@", data);
                   } else {
                       _state = @"error";
                       NSLog(@"received data is invalid.");
                   }
                   if (![_state isEqualToString:@"inProgress"]) {
                       dispatch_semaphore_signal(_semaphore);
                       // Stop timer
                       [timer invalidate];
                   }
               }];
    [task resume];
}

运行代码后

[runLoop run];
它什么都没发生! 那么,代码有什么问题?

调用dispatch_semaphore_wait将阻止线程,直到调用dispatch_semaphore_signal。这意味着必须从不同的线程调用信号,因为当前线程被完全阻塞。此外,您永远不应该从主线程调用wait,只能从后台线程调用wait。 这有用吗?

1 个答案:

答案 0 :(得分:0)

有几点意见:

  1. 您不必使用信号量。 run不会返回,直到计时器失效。

  2. The documentation run建议您不要使用您已经概述的方法,而是使用runMode:beforeDate:循环,如下所示:

    _shouldKeepRunning = true;
    while (_shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
        // this is intentionally blank
    }
    

    然后,在您使计时器无效的位置,您可以将_shouldKeepRunning设置为false

  3. 这种旋转运行循环以运行计时器的概念,没有任何违法行为,有点过时了。如果我真的需要在后台线程上运行一个计时器,我会使用一个调度计时器,就像https://stackoverflow.com/a/23144007/1271826的前半部分所述。为这样的事情启动runloop是一种效率低下的模式。

  4. 但是让我们退一步看看你想要实现的目标,我假设你正试图在某个服务器上查询一些状态,并希望在收到时通知你#39; s不再处于"正在进行中#34;州。如果是这样,我会采用异步模式,例如完成处理程序,并执行以下操作:

    - (void)checkStatus:(void (^ _Nonnull)(NSString *))completionHandler {
        // Send the url-request.
        NSURLSessionDataTask* task = [_session dataTaskWithRequest:_request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            NSString *state = ...
    
            if (![state isEqualToString:@"inProgress"]) {
                completionHandler(@"inProgress");
            } else {
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [self checkStatus:completionHandler];
                });
            }
        }];
        [task resume];
    }
    

    然后像这样调用它:

    [self checkStatus:^(NSString *state) {
        // can look at `state` here
    }];
    
    // but not here, because the above is called asynchronously (i.e. later)
    

    完全不需要运行循环。我也消除了计时器模式,而在先前请求完成后再次尝试 x 秒#34;,因为如果一个请求没有完成,则可能会出现计时器问题。下一个计时器开火的时间。 (是的,我知道您可以通过引入额外的状态变量来解决这个问题,以便跟踪您当前是否处于请求状态,但这很愚蠢。)