GCD and callbacks - concurrency issue

冷暖自知 提交于 2019-12-10 14:11:57

问题


I have a callback handler registered that listens to changes in the iOS Address Book. Due to some strange reason (for which a bug has been filed), this callback can sometimes be called more than once when the app returns from the background. I want my callback handler to run it's logic only once, even in cases the callback is called multiple times. This is how I register the callback:

ABAddressBookRegisterExternalChangeCallback(address_book, adressBookChanged, self);

This is how I structured my callback handler to take advantage of GCD to handle this. Unfortunately, it's not working, and GCD doesn't prevent the internal logic to be called twice...

void adressBookChanged(ABAddressBookRef ab, CFDictionaryRef info, void 
                       *context) 
{ 
    NSLog(@"** IN addressBookChanged callback!");

    ABAddressBookUnregisterExternalChangeCallback (ab, adressBookChanged, context);

    __block BOOL fireOnce = FALSE;
    dispatch_queue_t queue;
    queue = dispatch_queue_create("com.myapp.abcallback", NULL);

    dispatch_async(queue, ^{

        if (fireOnce == FALSE) {

            fireOnce = TRUE;

            dispatch_queue_t queueInternal;
            queueInternal = dispatch_queue_create("com.myapp.abcallbackInternal", NULL);
            dispatch_async (queueInternal, ^{
               NSLog(@"do internal logic");

            });

            dispatch_release(queueInternal);
        }
    });
    dispatch_release(queue);
}

I'm pretty sure this code works for receiving multiple notifications, so are callbacks different? Do they spawn different threads automatically, making the fireOnce value to be FALSE each time? How should I write this code to prevent multiple callbacks from calling the internal logic more than once? I suppose I could use locks and/or synchronized blocks to achieve this, but GCD seemed like a cleaner way to achieve this.


回答1:


The cause of multiple callbacks is due to the phone book iCloud background synchronization. Usually, if you have multiple devices logged in a same iCloud account, the synchronization will propagate to all devices, and echoed back to your testing device from where the change originated, thus, causes the callback to be invoked multiple times.

By the way, using a timer to constraint the duplicated invocations won't help resolve this issue completely, because you don't know when the next callback will be called depending on your network condition. You should instead program the logic to handle these duplicated invocations.




回答2:


I ended up using NSTimers instead of GCD to prevent the duplicate callbacks from firing my critical method. Much simpler, and works quite well!

[self.changeTimer invalidate];
self.changeTimer = nil;
self.changeTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                            target:self
                                                          selector:@selector(handleAdressBookExternalCallbackBackground)
                                                          userInfo:nil
                                                           repeats:NO];



回答3:


Whatever you are trying to use GCD for, you are negating any of its effects, since you are creating a queue for each time the callback is being called, and that queue is different from the others, so it always runs. You probably mean creating the queue outside of the callback and using it inside the callback (maybe a static global?).

Still, I don't understand how would that help you, since you would still be running each GCD block each time a callback fired. Unless your do internal logic part marks a record as has having been updated and you check this flag in your queued methods affecting the same record, you are still going to be running multiple times your code, GCD or not.




回答4:


Not really a direct answer to you GCD question, but I think that every time a unique 'context' is provided when you register, this creates a new 'registration' such that you are called back for each 'context'. You may be able to avoid being called multiple times by providing the same 'context'.




回答5:


I had similar problem. My solution was to save flag in NSUserDefaults, enable this flag after first addressbookChanged method, and disable it again, after my actions were done..

void MyAddressBookExternalChangeCallback (ABAddressBookRef notifyAddressBook,CFDictionaryRef info,void *context)
{
   NSLog(@"in MyAddressBook External Change Callback");

    if([[[NSUserDefaults standardUserDefaults]objectForKey:@"addressBookChanged"] boolValue] == NO)
      {
       [[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"addressBookChanged"];
       [[NSUserDefaults standardUserDefaults] synchronize];

        //we save sync status to defaults to prevent duplicate call of this method

        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"addressBookSync"];
        [[NSUserDefaults standardUserDefaults]synchronize];

        [APICallWithCompletion:^(BOOL success, id object) {
            [[NSUserDefaults standardUserDefaults] setObject:@NO forKey:@"addressBookChanged"];
            [[NSUserDefaults standardUserDefaults] synchronize];
        }];
   }
}

While this may not be correct approach, it seems to be working for me, as my api call takes long enough to prevent duplicate call of this method... I guess you could replace it with

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [[NSUserDefaults standardUserDefaults] setObject:@NO forKey:@"addressBookChanged"];
    [[NSUserDefaults standardUserDefaults] synchronize];
});



回答6:


I spent almost 2 days behind this issues. Even I was using timer but that was creating more issues. For eg. if you set timer for 5 sec and in that time duration if you go to contacts again and make some changes and come to the app it will end up ignoring that change because 5 secs are not over yet. so for that change you will have to kill the app and rerun the application. I just did 2 steps and everything worked like magic On

- (void)applicationDidEnterBackground:(UIApplication *)application

method I am registering to external changes

    -(void) registerExternalChanges
{
    dispatch_async(dispatch_get_main_queue(), ^{
        ABAddressBookRef addressBookRef = [self takeAddressBookPermission];
        ABAddressBookRegisterExternalChangeCallback(addressBookRef, addressBookChanged , (__bridge void *)(self));
    });
}

And once you come to app after finish making changes in contacts database UnRegisterExternalChanges

 ABAddressBookUnregisterExternalChangeCallback(ntificationaddressbook, addressBookChanged,(context));

Thats it addressBookChanged method will only gets called once!!!




回答7:


To execute a piece of code exactly once with the help of GDC, you can do:

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
    a piece of code
});


来源:https://stackoverflow.com/questions/7116956/gcd-and-callbacks-concurrency-issue

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!