I'm using CoreBluetooth to connect and exchange data with a peripheral device using Bluetooth Low Energy. To connect to my peripheral I'm using the following method (for clarity, manager is an instance of the CBCentralManager class).
- (void)connectPeripheral:(CBPeripheral *)peripheral {
// Connects with the peripheral
[manager connectPeripheral:peripheral options:nil];
}
Now I wish to write the asynchronous version of this method using Grand Central Dispatch, blocks and semaphores. I would like to have a version that times out within a specific time interval. First I defined this method:
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);
});
}
This function basically contains three blocks: the first is the action block (e.g. connect) and the second and third one are the handler blocks which should be called whether the action code is performed within or after the timeout expiration, respectively.
What I did next is to turn my connectPeripheral: method into an asynchronous method by wrapping it inside the following method:
- (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");
});
}
Unfortunately my third block never gets called, even if no peripheral could be discovered by the manager until the timeout expires. Where do I fail?
Looks like there's no problems with GCD
i get you code and remade it a little bit to check timeout stuff easy, so changing secsForTask and secsForTimeout I can check what called:
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
block start
timeout
block end
on secsForTask = 1 and secsForTimeout = 2
block start
block end
completion
probably you have to check if everything is ok with your timeout, so you don't pass in method connectPeripheralAnsync:withinTimeout: timeout in nanosecs.
-[CBCentralManager connectPeripheral: options:] method is a non-block async method.
so, this method call alway pass through next line, immediately.
in your dispatchAsyncWithCompletionAndTimeout(...) method case.
the block that contains "-[CBCentralManager connectPeripheral: options:]" executed.
and then dispatch_semaphore_signal(semaphore); executed without blocking thread.
so, you always cannot get timeout.
-[CBCentralManager connectPeripheral: options:] call will response with
-[CBCentralDelegate centralManager: didConnect:] when connected successfully.
so, you should approach differently.
dispatch_semaphore_signal(semaphore); should call on -[CBCentralDelegate centralManager: didConnect:].
Related
I have a XCTTestClass that has an asynchronous setup method. It will take some amount of time (has to parse files, insert them in a bd, etc) and I want to make sure my tests only run after this setup is done.
How can I do this?
You can use semaphores to wait till you get the results back from your async call.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// Do your async call here
// Once you get the response back signal:
[self asyncCallWithCompletionBlock:^(id result) {
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
In your -setup method use either a semaphore as above or use dispatch_group. dispatch_group is my preferred approach.
#implementation XCTTestSubClass()
{
dispatch_group_t _dispatchGroup;
}
#end
-(id)init
{
_dispatchGroup = dispatch_group_create();
return [super init];
}
-(void)setup
{
dispatch_group_async(_dispatchGroup, dispatch_get_current_queue(), ^{
//your setup code here.
});
}
Then override -invokeTest and make sure the group blocks(setup) is done running.
-(void)invokeTest
{
dispatch_group_notify(group, dispatch_get_current_queue(), ^{
[super invokeTest];
});
}
This guarantees that the tests will run only after -setup is completed.
I have a senario that requires me to make multiple call to a web api. The following is an example.
getDataAsync:(NSDictionary *)dictionary withCompletion: (void (^)(NSDictionary*))completion {
__block int counter = n; // the number of async blocks
__block NSMutableDictionary *output = [[NSMutableDictionary alloc] init];
void (^returnBlock)(void) = ^{
counter--;
if(counter != 0) return;
completion(#{#"return": output});
return;
};
void (^getResourceA)(void) = ^{
[service getResourceA : dictionary[#"idA"] completion:
^(ServiceResult results, MyResourceA *a, NSString *errMsg) {
[output setValue:a.value forKey:a.name];
returnBlock();
}];
};
// followed by n-1 other blocks like getResourceA
//...
}
I want to use the built in dispatch_queue rather than my own custom solution here. How can I do that given the inner completion block used by the asynchronous service call?
Also any other advice on how to go about this would be appreciated.
Dispatch groups have been invented for this purpose:
dispatch_group_t requestGroup = dispatch_group_create();
dispatch_group_async(requestGroup, queue, ^{
// ...
});
dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
completionBlock();
Or instead of waiting:
dispatch_group_notify(requestGroup, dispatch_get_main_queue(), ^{
completionBlock();
});
Also, instead of dispatching blocks to the group, you can also enter and leave a group manually, which works well with asynchronous service APIs:
dispatch_group_enter(requestGroup);
[service getResourceA : dictionary[#"idA"] completion: ^(ServiceResult results, MyResourceA *a, NSString *errMsg) {
[output setValue:a.value forKey:a.name];
dispatch_group_leave(requestGroup);
}];
Use dispatch_group_t. See Waiting on Groups of Queued Tasks.
The topic doesn't mention it, but use dispatch_group_notify to register a block instead of waiting inline.
I'm trying to send some images file (almost 100MB) to my iDevice clients using GCDAsyncSocket.
I want to Synchronously send packets to the clients. I mean after sending 100MB of data to first client iterating to the next client.but because of Asynchronous nature of GCDAsyncSocket I don't know how can I serialize these packet sending.
I can't use semaphore because before sending images I negotiate with each client to know what images I should send then try to send those images. and I can't find a neat way to wait and signal the semaphore.
- (void)sendImagesToTheClients:clients
{
...
//negotiating with client to know which images should sent
...
for(Client* client in clients)
{
packet = [packet addImages: images];
[self sendPacket:packet toClient:client];
}
}
- (void)sendPacket:packet toClient:client
{
// Initialize Buffer
NSMutableData *buffer = [[NSMutableData alloc] init];
NSData *bufferData = [NSKeyedArchiver archivedDataWithRootObject:packet];
uint64_t headerLength = [bufferData length];
[buffer appendBytes:&headerLength length:sizeof(uint64_t)];
[buffer appendBytes:[bufferData bytes] length:[bufferData length]];
// Write Buffer
[client.socket writeData:buffer withTimeout:-1.0 tag:0];
}
this is how AsyncSocket writing data works:
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
{
if ([data length] == 0) return;
GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
dispatch_async(socketQueue, ^{ #autoreleasepool {
LogTrace();
if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
{
[writeQueue addObject:packet];
[self maybeDequeueWrite];
}
}});
// Do not rely on the block being run in order to release the packet,
// as the queue might get released without the block completing.
}
so how can I synchronize this task?
UPDATE
for socket connection I use GCDAsyncSocket which heavily uses delegation for event notification.(GCDAsyncSocket.h and GCDAsyncSocket.m) (no method with completionHandler).
I have written a class named TCPClient which handles socket connection and packet sending and set it as the delegate of initialized socket.
after writing a packet, the delegate method - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag gets called. which only informs me some data has been written. here I can't decide based of written data to call dispatch_group_leave. so relying delegate method is useless.
I have modified [client.socket writeData:buffer withTimeout:-1.0 tag:0] in GCDAsyncSocket.h and .m files to accept a completionBlock: [client.socket writeData:buffer withTimeout:-1.0 tag:0 completionBlock:completionBlock]
using this approach helps me to solve synchronizing async tasks.
// perform your async task
dispatch_async(self.socketQueue, ^{
[self sendPacket:packet toClient:client withCompletion:^(BOOL finished, NSError *error)
{
if (finished) {
NSLog(#"images file sending finished");
//leave the group when you're done
dispatch_group_leave(group);
}
else if (!finished && error)
{
NSLog(#"images file sending FAILED");
}
}];
but the problem is after updating GCDAsyncsocket, my code may break.
here I'm looking for neat way to add completion handler to GCDAsyncsocket without modifying it directly. like creating a wrapper around it or using features of objective-c runtime.
do you have any idea?
You can accomplish this with dispatch groups. For a async task with a completion block:
//create & enter the group
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// perform your async task
dispatch_async(socketQueue, ^{
//leave the group when you're done
dispatch_group_leave(group);
});
// wait for the group to complete
// (you can use DISPATCH_TIME_FOREVER to wait forever)
long status = dispatch_group_wait(group,
dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
// check to see if it timed out, or completed
if (status != 0) {
// timed out
}
Alternatively for a task with a delegate:
#property (nonatomic) dispatch_group_t group;
-(BOOL)doWorkSynchronously {
self.group = dispatch_group_create();
dispatch_group_enter(self.group);
[object doAsyncWorkWithDelegate:self];
long status = dispatch_group_wait(self.group,
dispatch_time(DISPATCH_TIME_NOW,NSEC_PER_SEC * COMMAND_TIMEOUT));
// A
if (status != 0) {
// timed out
return NO
}
return YES;
}
-(void)asyncWorkCompleted {}
// after this call, control should jump back to point A in the doWorkSynchronously method
dispatch_group_leave(self.group);
}
I have a series of dispatch_async that I am performing and I would like to only update the UI when they are all done. Problem is the method within dispatch_async calls something in a separate thread so it returns before the data is fully loaded and dispatch_group_notify is called before everything is loaded.
So I introduce a infinite loop to make it wait until a flag is set.
Is this the best way? See code below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
for (...) {
dispatch_group_async(group, queue, ^{
__block BOOL dataLoaded = NO;
[thirdPartyCodeCallWithCompletion:^{
dataLoaded = YES;
}];
// prevent infinite loop
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
queue, ^{
dataLoaded = YES;
});
// infinite loop to wait until data is loaded
while (1) {
if (dataLoaded) break;
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//update UI
});
}
You're already aware of dispatch groups. Why not just use dispatch_group_wait(), which includes support for a timeout? You can use dispatch_group_enter() and dispatch_group_leave() rather than dispatch_group_async() to make the group not done until the internal block for the third-party call with completion is finished.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
for (...) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[thirdPartyCodeCallWithCompletion:^{
dispatch_group_leave(group);
}];
}
}
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSECS_PER_SEC));
dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
The use of dispatch_group_wait() does make this code synchronous, which is bad if run on the main thread. Depending on what exactly is supposed to happen if it times out, you could use dispatch_group_notify() as you were and use dispatch_after() to just updates the UI rather than trying to pretend the block completed.
Update: I tweaked my code to make sure that "update UI" happens on the main queue, just in case this code isn't already on the main thread.
By the way, I only used dispatch_async() for the block which calls thirdPartyCodeCallWithCompletion: because your original used dispatch_group_async() and I wasn't sure that the hypothetical method was asynchronous. Most APIs which take a completion block are asynchronous, though. If that one is, then you can just invoke it directly.
Another method is to use semaphore and the dispatch_semaphore_wait:
// Create your semaphore, 0 is specifying the initial pool size
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
#autoreleasepool {
// Your code goes here
}
// Release the resource and signal the semaphore
dispatch_semaphore_signal(semaphore);
});
// Wait for the above block execution, AKA Waits for (decrements) a semaphore.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// After this line you can now safely assert anything you want regarding the async operation since it is done.
If libsqlite is not thread safe a code like that
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSArray *__albumsCollection = albumCollections;
dispatch_apply(count, queue, ^(size_t i)
{
MPMediaItem *albumObj = [[__albumsCollection objectAtIndex:i] representativeItem];
///// making some sqlite queries
});
would raise a BAD_EXEC.
So how to make this code thread safe?
My solution was using the main queue
dispatch_apply(count, dispatch_get_main_queue(), ^(size_t i)
{
/// my sqllite queries
});
but I'm not satisfied with that. How to make it better?
Instead of using dispatch_get_main_queue() to get the main queue you might want to create a separate private dispatch queue on a non-main thread like so:
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_SERIAL); // or NULL as last parameter if prior to OS X 10.7/iOS 5.0
dispatch_apply(count, queue, ^(size_t i) {
/// your SQLite queries
});
Alternatively you could use a FMDatabaseQueue from Gus Mueller's (#ccgus) brilliant FMDB SQLite wrapper framework (which is what I'd do):
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
// Your SQLite queries:
[db executeQuery:#"...", ...];
...
}];
…which will send your query block to a serial dispatch queue, wrapping its execution synchronously.
Not convinced yet?
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
// Your SQLite queries:
[db executeQuery:#"...", ...];
...
}];
How about now?
Also, custom-defined block-based SQLite functions.
dispatch_queue_t q=dispatch_queue_create("name", NULL);
dispatch_sync(q, ^{
//your code is here
});
dispatch_async(dispatch_get_main_queue(), ^(void) {
//as soon as above block is completed, this block executed and you will be notified that
//work is completed
});