2

I am in a situation where I need to call multiple web service requests at a time to call to my server to delete messages on the server and I am having a difficult time trying to figure out the best way to trigger some methods to refresh my data at the completion of these group of web service calls.

From what I have researched using a semaphore counter should work for what I am wanting to do, but I am running into a problem where upon calling dispatch_release() on my semaphore token my application crashes with this -

libdispatch.dylib`_dispatch_semaphore_dispose$VARIANT$mp:
0x3c03ed70:  push   {r7, lr}
0x3c03ed72:  ldr    r1, [r0, #40]
0x3c03ed74:  mov    r7, sp
0x3c03ed76:  ldr    r2, [r0, #36]
0x3c03ed78:  cmp    r1, r2
0x3c03ed7a:  bge    0x3c03ed7e                ; _dispatch_semaphore_dispose$VARIANT$mp + 14
0x3c03ed7c:  trap   

Everything i've found on this problem points to the semaphore token being referenced by something but I can't see what would have a reference to the token.

- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
    NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];

    long messagesCount = [messages count] - 1;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(messagesCount);

    BOOL (^isLastRequest)(void) = ^BOOL (void) {
        long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
        if (0 == result) {
            return false;
        }
        dispatch_release(semaphore);
        return true;
    };

    for (MSMMessageDataModel *message in messages) {
        NSDictionary *dictionary = @{@"license": userDictionary[@"license"],
                                     @"messageID" : message.msgID};
        NSLog(@"Called this many times! %@", message.msgID);
        [[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
            if (error) {
                isLastRequest();

                completion(NO);
            } else {
                if (isLastRequest()) {
                    completion(YES);
                }
            }
        } andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:@"DeleteThread"
                                                                         withParameters:dictionary]];
    }
}

EDIT

Thanks for the great answers. As Dustin said I was attempting to use dispatch_semaphore for something that it should not be used for. I accepted his answer because it was simple to implement and didn't need any re-structure of what i'm doing currently to send my web services. I now have some good reading material though about dispatch_groups in general though!

Thanks for all your help!

4

3 回答 3

3

I'm not sure this is the most efficient way to solve your problem, but just looking through your code, one problem you might be running into is that you're creating the semaphore with a count and trying to release it when that count is less than the initial count. In GCD, always create a semaphore with 0 and then signal the semaphore to the correct number of "resources" you need to count. The reason is because semaphores can't be destroyed/released if their count is less than the initial count. It kinda makes sense if you think of it as a resource counter. Having a semaphore number less than the initial count means that you have a worker still using one of the resources.

You can see this code here http://opensource.apple.com/source/libdispatch/libdispatch-187.7/src/semaphore.c. The code that will throw the exception in _dispatch_semaphore_dispose is:

if (dsema->dsema_value < dsema->dsema_orig) {
    DISPATCH_CLIENT_CRASH(
            "Semaphore/group object deallocated while in use");
}
于 2013-06-04T21:16:16.633 回答
2

This is exactly what dispatch_group is designed to address. You dispatch several blocks to a group, and when they have all completed, another block will be executed (or you can wait on them if you need a synchronous behavior).

First see Waiting on Groups of Queued Tasks in the Concurrency Programming Guide, and see the dispatch_group functions in the GCD reference. To see them in action, see the JuliaCell example from Chapter 13 of iOS:PTL. Cocoa Samurai also has some examples.

Even if you can't actually dispatch the blocks to a group (it may not work with how MSMClient operates), you can still use dispatch groups manually by calling dispatch_group_enter() and dispatch_group_leave() to get the same behavior you're trying to get from the semaphore.

As a side note, BOOL is not the same as bool. BOOL return YES and NO, which are different than true and false (which I assume means you're compiling this as ObjC++, which always makes me shudder, but that's a different issue). Mixing them can matter because they can be (and sometimes are) different sizes. I've had to crashes due to that personally.

于 2013-06-05T02:19:41.730 回答
1

This is an incorrect usage for dispatch_semaphore -- it isn't meant to do what you are attempting. What you need is a counting variable that is thread safe. You can get this using __sync_sub_and_fetch.

- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
    NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];

    __block long messagesCount = [messages count];

    for (MSMMessageDataModel *message in messages) {
        NSDictionary *dictionary = @{@"license": userDictionary[@"license"],
        @"messageID" : message.msgID};
        NSLog(@"Called this many times! %@", message.msgID);
        [[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
            long which = __sync_sub_and_fetch(&messageCount, 1);
            if(which == 0)
                completion(error == nil);
        } andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:@"DeleteThread"
                                                                                    withParameters:dictionary]];
    }
}

__sync_sub_and_fetch tells the CPU that you want it to take latest version of 'messageCount' from all threads (and cores), subtract 1, and give you the result.

于 2013-06-05T00:45:45.080 回答