使用GCD和信号量获取异步方法

时间:2015-12-21 11:50:55

标签: objective-c grand-central-dispatch semaphore

我正在使用CoreBluetooth使用Bluetooth Low Energy与外围设备连接和交换数据。要连接到我的外围设备,我使用以下方法(为清楚起见,managerCBCentralManager类的实例)。

- (void)connectPeripheral:(CBPeripheral *)peripheral {
    // Connects with the peripheral
    [manager connectPeripheral:peripheral options:nil];
}

现在我希望使用Grand Central Dispatch,块和信号量编写此方法的异步版本。我想有一个在特定时间间隔内超时的版本。首先我定义了这个方法:

void dispatchAsyncWithCompletionAndTimeout(dispatch_queue_t queue, int64_t timeoutInNanoseconds,
                                          dispatch_block_t block, dispatch_block_t completionBlock,
                                          dispatch_block_t timeoutBlock) {
    NSCParameterAssert(queue);
    NSCParameterAssert(timeoutInNanoseconds >= 0);
    NSCParameterAssert(block);

    dispatch_async(queue, ^{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, timeoutInNanoseconds);

    dispatch_async(queue, ^{
        long timedOut = dispatch_semaphore_wait(semaphore, timeoutTime);
        if (timedOut) {
            if (timeoutBlock) {
                timeoutBlock();
            }
        } else if (completionBlock) {
            completionBlock();
        }
    });

    block();
    dispatch_semaphore_signal(semaphore);
  });
}

此函数基本上包含三个块:第一个是动作块(例如connect),第二个和第三个是处理程序块,无论动作代码是在超时到期时间内还是在超时到期后执行,都应该调用它。 / p>

我接下来要做的是将connectPeripheral:方法转换为异步方法,方法是将其包含在以下方法中:

- (void)connectPeripheralAnsync:(CBPeripheral *)peripheral withinTimeout:(NSInteger)timeout {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatchAsyncWithCompletionAndTimeout(queue, (int64_t)timeout * NSEC_PER_SEC, ^{
        [self connectPeripheral:peripheral];
    }, ^{
        NSLog(@"Peripheral discovered");
    }, ^{
        NSLog(@"Time runned out");
    });
}

不幸的是,我的第三个块永远不会被调用,即使在超时到期之前管理器也找不到外围设备。我在哪里失败?

2 个答案:

答案 0 :(得分:1)

看起来GCD没有问题

我得到你的代码并重新制作一点点来检查超时的东西很简单,所以改变secsForTask和secsForTimeout我可以查看所谓的:

unsigned int secsForTask       = 3;
unsigned int secsForTimeout    = 2;

    dispatch_queue_t queue = dispatch_queue_create("com.test.111", DISPATCH_QUEUE_CONCURRENT);

    int64_t timeoutInNanoseconds = secsForTimeout * NSEC_PER_SEC;

    dispatch_async(queue, ^{

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, timeoutInNanoseconds);

        dispatch_block_t timeoutBlock = ^{
            NSLog(@"timeout");
        };
        dispatch_block_t completionBlock = ^{
            NSLog(@"completion");
        };
        dispatch_block_t block = ^{
            NSLog(@"block start");
            sleep(secsForTask);
            NSLog(@"block end");
        };

        dispatch_async(queue, ^{
            long timedOut = dispatch_semaphore_wait(semaphore, timeoutTime);
            if (timedOut) {
                if (timeoutBlock) {
                    timeoutBlock();
                }
            } else if (completionBlock) {
                completionBlock();
            }
        });

        block();
        dispatch_semaphore_signal(semaphore);

    });

on secsForTask = 3 and secsForTimeout = 2

  

阻止开始
  超时
  阻止结束

on secsForTask = 1 and secsForTimeout = 2

  

阻止开始
  阻止结束   完成

可能你必须检查你的超时是否一切正常,所以你没有传入方法connectPeripheralAnsync:withinTimeout:timeout in nanosecs。

答案 1 :(得分:0)

-[CBCentralManager connectPeripheral:options:]方法是一种非阻塞异步方法。 因此,此方法调用总是立即通过下一行。

在您的dispatchAsyncWithCompletionAndTimeout(...)方法的情况下。 执行包含“-[CBCentralManager connectPeripheral:options:]”的块。 然后dispatch_semaphore_signal(semaphore);在不阻塞线程的情况下执行。 因此,您总是无法超时。

-[CBCentralManager connectPeripheral:options:]调用将响应 -[[CBCentralDelegate centralManager:didConnect:]连接成功时。

因此,您应该使用不同的方法。 dispatch_semaphore_signal(semaphore);应该调用-[CBCentralDelegate centralManager:didConnect:]。