I try to use the Bluetooth communication synchronously. I send the data to the BTLE device and wait for the response to continue in the same method unless a timeout occurs.
I wanted to use NSRUNLOOP for the wait. However, I have the problem that only when the loop is completed, the data received in the meantime can be processed. So the loop seems to block the processing of the data.
The data is sent in a separate thread
... create command data
if (![self sendCommand:nd_commandData timeOutMesc:ui_timeOutMSec command:ui_command error:error])
{
// Timeout
return false;
}
... continue working with the received data
sending method with timeout:
-(BOOL)sendCommand:(NSData *)nd_sendData timeOutMesc:(uint)ui_timeOutMSec command:(UInt16)ui_command
{
b_exitConditionSleep = false;
b_timeOutOccurred = false;
self.ni_totalResponseLength = 0;
self.ni_expectedResponseLength = 0;
// Clear buffer
memset(&uia_receivedDataBytes[0], 0x00, sizeof(uia_receivedDataBytes));
[[BTLE_Communicator sharedInstance] setDelegate:self];
// send data
[[BTLE_Communicator sharedInstance] sendDataFromAppToBLEDevice:nd_sendData];
BOOL b_rechTimeOut = true;
NSDate *start = [NSDate date];
NSTimeInterval timeInterval;
uint ui_differenz;
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.01];
while (!b_exitConditionSleep && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
{
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.01];
timeInterval = [start timeIntervalSinceNow] * -1;
ui_differenz = round(timeInterval * 1000);
if (ui_differenz >= ui_timeOutMSec) {
NSLog(#"Command: 0x%hX - Timeout reached: %u", ui_command, ui_differenz);
b_rechTimeOut = false;
b_timeOutOccurred = true;
break;
}
}
return b_rechTimeOut;
}
receiving method:
-(void)communicatorPeripheralDidReceiveResponseUARTData:(NSData *)nd_data
{
NSLog(#"ResponseUARTData %#", nd_data);
if (!b_timeOutOccurred)
{
b_exitConditionSleep = true;
[self processResponseData:nd_data];
}
}
Maybe someone has an indication of what I'm doing wrong.
what I'm doing wrong
You're trying to make an asynchronous task synchronous. That's the core problem. As for the specific reason why this particular pattern won't work, it is quite likely that the nested run loop is preventing a callback from being processed properly. Or it might be some other implementation detail that is incompatible with the conversion from asynchronous to synchronous.
Beyond an intellectual curiosity, there isn't much reason to figure out why.
Instead, call some kind of an update method from your implementation of communicatorPeripheralDidReceiveResponseUARTData:. This will allow the processing to be asynchronous, as intended.
Related
I've some data which is accumulated in a buffer and I need to read the data when buffer is having data. This i need to do with thread synchronisation. I've worked little with GCD, which I'm failing to do. please help how to do a circular buffer with read and write threads in synchronization.
My Code:
- (void)viewDidLoad {
[super viewDidLoad];
readPtr = 0;
writePtr = 0;
currentPtr = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
while(YES){
[self writeToBuffer:buffer[0] withBufferSize:bufferSize];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
while(YES){
float* newBuffer;
if(currentPtr>512){
newBuffer = [self readBuffer];
}else{
continue;
}
[self UseBuffer: newBuffer];
}
});
}
-(void)writeToBuffer:(float*)Values withBufferSize:(int)bSize{
[_lock lock];
for(int i=0;i<bSize;i++){
if(writePtr>1859){
writePtr = 0;
}
globalBuffer[writePtr] = Values[i];
writePtr++;
currentPtr++;
}
NSLog(#"Writing");
[_lock unlock];
}
-(float*)readBuffer{
[_lock lock];
float rBuffer[512];
for(int i=0;i<512;i++){
if(readPtr>1859){
readPtr = 0;
}
rBuffer[i] = globalBuffer[readPtr];
readPtr++;
currentPtr--;
}
NSLog(#"Reading");
[_lock unlock]
return rBuffer;
}
One of the key points of GCD is that it completely replaces the need for locks. So, if you are mixing GCD and mutex locks it is typically a sign that you're doing things wrong or sub-optimally.
A serial queue is, effectively, an exclusive lock on whatever is associated with the serial queue.
There a bunch of problems in your code.
while (YES) {...} is going to spin, burning CPU cycles ad infinitum.
The readBuffer method is returning a pointer to a stack based buffer. That won't work.
It isn't really clear what the goal of the code is, but those are some specific issues.
The iOS application that I work on has an apple watch app that goes along with it. We recently started getting complaints that the GPS distance updates have slowed down and the watch is a few seconds behind the phone. I have been looking into this and wrote some test code, the reply block from [[WCSession defaultSession] sendMessage:message
replyHandler:replyHandler
errorHandler:errorHandler
is definitely twice as slow in watchOS 2.2 vs 2.1. I have attached the test code below.
#pragma mark - Location Update.
/**
* #description Provides an NSBlockOperation to be executed in an operation queue. This is an attempt to force serial
* processing
*/
- (NSBlockOperation*)distanceUpdateBlock {
NSBlockOperation *blockOp = [[NSBlockOperation alloc] init];
__weak NSBlockOperation * weakOp = blockOp;
__weak typeof(self) weakSelf = self;
[blockOp addExecutionBlock:^{
typeof(weakSelf) blockSafeSelf = weakSelf;
typeof(weakOp) blockSafeOp = weakOp;
if (!blockSafeOp.isCancelled) { // Make sure we haven't already been cancelled.
__block NSDate *startDate = [NSDate date];
__block BOOL completed = NO;
void (^replyBlock)(NSDictionary*) = ^(NSDictionary *message){
if (!blockSafeOp.isCancelled) {
[blockSafeSelf processUserLocationOnWatch:message];
double replyTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSLog(#"Reply Time: %.03f", replyTime);
completed = YES;
}
};
void (^failBlock)(NSError*) = ^(NSError *error) {
if (!blockSafeOp.isCancelled) {
double replyTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSLog(#"Reply Time Fail: %.03f", replyTime);
completed = YES;
}
};
[self fetchUserLocationFromIphoneWithReplyHandler:replyBlock errorHandler:failBlock];
do {
usleep(10000); // 1/100th second wait to throttle evaluations (Don't worry - in final code I will subclass NSOperation and control when it actually finishes - this is for easy testing.)
} while (!completed && !blockSafeOp.isCancelled && [blockSafeSelf isWatchReachable]); //(isWatchReachable just makes sure we have a session with the phone and it is reachable).
}
}];
blockOp.completionBlock = ^{
typeof(weakSelf) blockSafeSelf = weakSelf;
typeof(weakOp) blockSafeOp = weakOp;
if (!blockSafeOp.isCancelled) {
[blockSafeSelf addOperationForLocationUpdate]; // since we are finished - add another operation.
}
};
return blockOp;
}
- (void)addOperationForLocationUpdate {
[distanceUpdateOperationQueue addOperation:[self distanceUpdateBlock]];
}
- (void)startUpdatingLocation {
[self addOperationForLocationUpdate];
}
- (void)stopUpdatingLocation {
[distanceUpdateOperationQueue cancelAllOperations];
}
- (void)fetchUserLocationFromIphoneWithReplyHandler:(nullable void (^)(NSDictionary<NSString *, id> *replyMessage))replyHandler errorHandler:(nullable void (^)(NSError *error))errorHandler {
if (self.isSessionActive) {
NSDictionary *message = #{kWatchSessionMessageTag:kWatchSessionMessageUserLocation};
if (self.isWatchReachable) {
[[WCSession defaultSession] sendMessage:message
replyHandler:replyHandler
errorHandler:errorHandler
];
} else {
errorHandler(nil);
}
} else {
[self activateSession];
errorHandler(nil);
}
}
The handler on the iPhone side simply get's the User location and calls the replyHandler with the encoded information.
The logs for time on 2.2 look like (consistently about a second)
Reply Time: 0.919
Reply Time: 0.952
Reply Time: 0.991
Reply Time: 0.981
Reply Time: 0.963
Same code on 2.1 looks like
Reply Time: 0.424
Reply Time: 0.421
Reply Time: 0.433
Reply Time: 0.419
Also, I've noticed that after 5 mins (300 seconds) the error handlers start getting called on the messages that have already had the reply handler called. I saw another thread where someone mentioned this as well, is anyone else having this happen and know why?
So, Q1 - has anyone run into this performance slow down and figured out how to keep the replyHandler running faster, or found a faster way to get updates?
Q2 - Solution for the errorHandler getting called after 5 mins.
Just a few things to eliminate - I have done my due diligence on testing the iOS code between receiving the message and calling the replyHandler. There is no change in the time to process between iOS 9.2/9.3. I have narrowed it down to this call. In fact, the way we did this in previous versions is now backing up the sendMessage's operationQueue. So now I am forcing a one at a time call with this test code. We don't get backed up anymore, but the individual calls are slow.
So, I was running tests today on the same piece of code, using the same devices, and although the code has not changed, it is now running twice as fast as it initially did (in 2.1). Logs are coming in the range of .12 - .2 seconds. Only thing that has happened since then is software updates. Also, the fail block is no longer called after 5 mins. So both parts of this question are magically working. I am currently using iOS 9.3.4(13G35) and watch is at 2.2.2. Seems to have been an OS issue somewhere in the chain of processing the queue between the watch and the phone. All is good now.
I have two methods which run on a serial queue. Each method return a copy of some class. I'm trying to achieve thread safety solution while also mainting data integrity.
for example:
-(Users *) getAllUsers
{
__block copiedUsers;
dispatch_sync(_backgroundQueue, ^{
copiedUsers = [self.users copy]; // return copy object to calling thread.
});
return copiedUsers;
}
-(Orders *) getAllOrders
{
__block copiedOrders;
dispatch_sync(_backgroundQueue, ^{
copiedOrders = [self.Orders copy]; // return copy object to calling thread.
});
return copiedOrders;
}
In addition to this two methods, I have a worker class that add/remove users and orders, all done via a serial queue backgroundQueue.
If in the main thread I call getAllUsers and then getAllOrders right after the other my data integrity isn't safe because between the two calls the worker class might have changed the model.
my question is how can I make to the caller a nice interface that allows multiple methods to run atomically?
Model is only updated from backgroundQueue serial queue.
Client talks to model via a method that receives a block that runs in the background queue.
In addition, not to freeze main thread, I create another queue and run a block that talks with the gateway method.
P.S - attention that dispatch_sync is called only in runBlockAndGetNeededDataSafely to avoid deadlocks.
Code sample:
aViewController.m
ManagerClass *m = [ManagerClass new];
dispatch_queue_t q = dispatch_queue_create("funnelQueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block_q = ^{
__Users *users;
__Orders *orders;
[manager runBlockAndGetNeededDataSafely:^
{
users = [manager getUsers];
orders = [manager getOrders];
dispatch_async(dispatch_get_main_queue(),
^{
// got data safely - no thread issues, copied objects. update UI!
[self refreshViewWithUsers:users
orders:orders];
});
}];
}
dispatch_async(q, block_q);
Manager.m implementation:
-(void) runBlockInBackground:(dispatch_block_t) block
{
dispatch_sync(self.backgroundQueue, block);
}
-(Users *) getAllUsers
{
return [self.users copy];
}
-(Orders *) getAllOrders
{
return [self.Orders copy];
}
To answer your question about how to checking the current queue:
First when you create the queue, give it a tag:
static void* queueTag = &queueTag;
dispatch_queue_t queue = dispatch_queue_create("a queue", 0);
dispatch_queue_set_specific(queue, queueTag, queueTag, NULL);
and then run a block like this:
-(void)runBlock:(void(^)()) block
{
if (dispatch_get_specific(queueTag) != NULL) {
block();
}else {
dispatch_async(self.queue, block);
}
}
Your example doesn't work. I suggest to use completion callback. You should have an option to know when the worker finish his job to return to value.
- (void)waitForCompletion:(BOOL*)conditions length:(int)len timeOut:(NSInteger)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
BOOL done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
//update done
done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
} while (!done);
}
-(void) getAllUsers:(void(^)(User* user, NSError* error))completion
{
dispatch_async(_backgroundQueue, ^{
BOOL condition[2] = [self.userCondition, self.orderCondition];
[self waitForCompletion: &condition[0] length:2 timeOut:60];
if (completion) {
completion([self.users copy], nil);
}
});
}
I needed to sync some background threads I got in my iOS app. I needed something to hold the execution of one thread while the other did its job, in order to keep the execution of the code linear.
I've been fighting with this code all day, and after a lot of reading I came up with this code:
-(void)waitOne:(double)timeout
{
if (!shouldRun)
{
shouldRun = true;
double runs = 0;
do {
NSDate* next = [NSDate dateWithTimeIntervalSinceNow:0.5];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:next];
runs++;
} while ((runs/2) < timeout && shouldRun);
}
else
{
#throw [NSException exceptionWithName:#"InvalidHandle" reason:#"The currenct handle is in use" userInfo:nil];
}
}
I can tell you that it got the job done. But I also found some bad feedback at the forum where I found the idea for that algorithm, saying that "it'll make your loop busy wait" and that was a bad idea.
So, I come here to ask. Is there a better way to accomplish what I want?
Look into [NSCondition] which enables you to wait and signal threads
Basically you allocate a NSCondition and in the block you'll have [condition wait]; which will cause the thread to wait. then, when the async operation is done, you call [condition signal]; which will signal the waiting thread to continue.
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/NSCondition_class/Reference/Reference.html
For future reference, if anyone needs, here's how my code ended up:
#implementation WaitHandle
#synthesize isWaiting;
-(void)waitOne:(double)timeout
{
if (!isWaiting)
{
isWaiting = true;
if (timeout <= 0) {
timeout = 0.1;
}
[handle waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]];
}
}
-(void)signal
{
[handle signal];
}
-(id)init
{
self = [super init];
if (self != nil)
{
isWaiting = false;
handle = [[NSCondition alloc] init];
}
return self;
}
-(void)dealloc
{
[self signal];
}
#end
I am using an NSOperation subclass (called PointsOperation) to do some calculations in the background in my app. Due to user behaviour, these calculations might need to be canceled, and new calculations started. In that case, I will create a new PointsOperation instance, and add it to the same NSOperationQueue as the first one. As the first thing in the main method of the PointsOperation, it will check whether another operation is already running, and cancel it.
Because the operations are using some shared caches, they cannot be (and don't need to be) running in parallel. Therefore, the second operation will wait until the first one has finished. The resulting code for the main method looks something like this:
static NSOperation *currentOperation = nil;
- (void) main
{
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
[currentOperation cancel];
[currentOperation waitUntilFinished];
}
currentOperation = self;
}
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
}
currentOperation = nil;
// releasing autorelease pool, etc
}
This all works fine, the first operation gets cancelled, and the second waits for it to finish, and then starts calculating.
The problem is: it takes 3-10 seconds between the first operation ending the main method, and the second one to come out of the waitUntilFinished.
Does anybody have seen this before and knows what to do about that?
I have also tried, instead of the waitUntilFinished, to make the second operation dependent on the first, with "addDependency:" (in the init method, rather than the main). That also works, but has the same problem: the start of the second operation is a number of seconds behind the finish of the first method.
Despite of its name, the cancel method does not magically cancel the operation.
Canceling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed.
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html
If you did not write your code to check isCancelled property, so the operation's thread will run to the end no matter you cancel it or not.
I tried to reproduce the issue here but my code just work fine with no such delay. This is my code:
#interface PointsOperation : NSOperation {
#private
bool calculationsFinished;
}
#property (nonatomic, assign) int tag;
#end
#implementation PointsOperation
#synthesize tag;
static NSOperation *currentOperation = nil;
static NSString* lock = #"LOCK";
- (void) main
{
NSLog(#"Before autoreleasepool with tag: %d", tag);
#autoreleasepool {
NSLog(#"Before lock");
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
NSLog(#"Before cancel");
[currentOperation cancel];
NSLog(#"Before waitUntilFinished");
NSDate* beforeWait = [NSDate date];
[currentOperation waitUntilFinished];
NSLog(#"After waitUntilFinished took %f seconds", [[NSDate date] timeIntervalSinceDate:beforeWait]);
}
currentOperation = self;
}
NSLog(#"Before while loop");
int i = 0;
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
[NSThread sleepForTimeInterval:1];
NSLog(#"Inside while loop = %d", i);
calculationsFinished = (++i > 10);
}
NSLog(#"After while loop: i = %d", i);
currentOperation = nil;
// releasing autorelease pool, etc
}
NSLog(#"%#", #"End of method");
}
#end
And here's how I use it:
NSOperationQueue* q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 4;
for (int i = 0; i < 10; i++) {
[q addOperation:[PointsOperation new]];
}
The result of time took by waitUntilFinished came in two categories:
After waitUntilFinished took 1.002624 seconds
and
After waitUntilFinished took 0.000749 seconds
which depends on the timing of the call I think.
Maybe you should provide more of your code if possible, as problem might be somewhere else in your code.