简体   繁体   中英

Why does the block after dispatch_async(dispatch_get_main_queue() never get called?

I want to execute some code asynchronously and have for this reason started using GCD for OSX/iOS.
Currently I am using the function dispatch_async() .
When I want to execute something concurrently on another thread, I use the function dispatch_get_global_queue() .
When I want to dispatch the result to the main thread, I use the function dispatch_get_main_queue() .
But the results never arrive to the main thread.

When setting up breakpoints in the debugger (at the dispatch_async lines), the block after function dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) gets executed only sometimes, but often the debugger just ignores it.
When the block does get executed and execution flow gets to the breakpoint at dispatch_async(dispatch_get_main_queue() , the block after that always gets ignored.

Without dispatch_async(dispatch_get_global_queue) and dispatch_async(dispatch_get_main_queue) the code executes as it should, albeit synchronously.

My question is why does dispatch_async(dispatch_get_main_queue() inside of the dispatch_async(dispatch_get_global_queue() block never get executed?
And on the same note, why is the dispatch_async(dispatch_get_global_queue() block not getting executed every time?

My development environment is
OS: OS X 10.11.3
IDE: Xcode 7.2
Compiler: Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0

Here is a simple example which reproduces the erratic behavior (an OS X console application):

TestClass.h

#ifndef TestClass_h
#define TestClass_h

@interface TestClass : NSObject {
}

- (void)testMethod:(NSString *)testString withCompletionBlock:(void(^)(NSString *blockResult, NSError __autoreleasing *error))completionBlock;

@end

#endif

TestClass.m

#import <Foundation/Foundation.h>
#import "TestClass.h"

@implementation TestClass

- (void)testMethod:(NSString *)testString withCompletionBlock:(void(^)(NSString *blockResult, NSError __autoreleasing *error))completionBlock {
    __block NSString *stringResult = nil;

    if (completionBlock) {
        __block NSError *resultError = nil;


       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // THIS BLOCK IS CALLED ONLY SOMETIMES, MOST OF THE TIME IT IS IGNORED.

            if ([testString isEqual: @"Error string"]) {
                NSDictionary *errorUserInfo = @{ NSLocalizedDescriptionKey: @"This is an error.", NSLocalizedFailureReasonErrorKey: @"", NSLocalizedRecoverySuggestionErrorKey: @"" };                
                resultError = [[NSError alloc] initWithDomain:@"com.test.TestErrorDomain" code:10 userInfo:errorUserInfo];
            }
            else {
                stringResult = testString;
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                // THIS BLOCK NEVER GETS EXECUTED.

                completionBlock(stringResult, resultError);
            });
        });
    }
}

@end

main.m

#import <Foundation/Foundation.h>
#import "TestClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSString *resultString = nil;
        TestClass * testObject = [[TestClass alloc] init];

        // Output for this call should be: The result string is: Test string.
        [testObject testMethod:@"Test string" withCompletionBlock:^(NSString *blockString, NSError __autoreleasing *error) {
            resultString = blockString;

            if (resultString) {
                NSLog(@"The result string is: %@.", resultString);
            }
        }];

        // Output for this call should be: Error: This is an error.
        [testObject testMethod:@"Error string" withCompletionBlock:^(NSString *blockString, NSError __autoreleasing *error) {
            resultString = blockString;

            if (!resultString) {
                if (error) {
                    NSLog(@"Error: %@", [error localizedDescription]);
                }
                else {
                    NSLog(@"Error not recognized!");
                }
            }
        }];
    }

    return 0;
}

That is because your TestClass object is released as soon as the main() function exits. Being mainly async, the testMethod: does not block the pointer in the main().

Try adding a semaphore at the end of the main(). This semaphore should be signaled in the testMethod dispatch_async(dispatch_get_main_queue(), ^{}); block.

Here is the working example with a semaphore, as suggested in @CharlesThierry's answer. I have also included some NSLogs that help illustrate the execution flow:

TestClass.h

#ifndef TestClass_h
#define TestClass_h

@interface TestClass : NSObject {
}

- (void)testMethod:(NSString *)testString withSemaphore:(dispatch_semaphore_t)sem withCompletionBlock:(void(^)(NSString *blockResult, NSError __autoreleasing *error))completionBlock;

@end

#endif

TestClass.m

#import <Foundation/Foundation.h>
#import "TestClass.h"

@implementation TestClass

- (void)testMethod:(NSString *)testString withSemaphore:(dispatch_semaphore_t)sem withCompletionBlock:(void(^)(NSString *blockResult, NSError __autoreleasing *error))completionBlock {
    __block NSString *stringResult = nil;

    if (completionBlock) {
        __block NSError *resultError = nil;

        NSLog(@"INSIDE TEST METHOD, OUTSIDE DISPATCH ASYNC");

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"INSIDE THE DISPATCH ASYNC TO THE GLOBAL QUEUE");

            if ([testString isEqual: @"Error string"]) {
                NSDictionary *errorUserInfo = @{ NSLocalizedDescriptionKey: @"This is an error.", NSLocalizedFailureReasonErrorKey: @"", NSLocalizedRecoverySuggestionErrorKey: @"" };
                resultError = [[NSError alloc] initWithDomain:@"com.test.TestErrorDomain" code:10 userInfo:errorUserInfo];
            }
            else {
                stringResult = testString;
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"INSIDE THE DISPATCH ASYNC TO THE MAIN QUEUE!");
                completionBlock(stringResult, resultError);
                dispatch_semaphore_signal(sem);
            });
        });
    }
}

@end

main.m

#import <Foundation/Foundation.h>
#import "TestClass.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"BEFORE BLOCK");

        __block NSString *resultString = nil;
        TestClass * testObject = [[TestClass alloc] init];
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);

        [testObject testMethod:@"Test string" withSemaphore:sem withCompletionBlock:^(NSString *blockString, NSError __autoreleasing *error)
         {
             NSLog(@"INSIDE FIRST BLOCK");

             resultString = blockString;

             if (resultString)
             {
                 NSLog(@"The result string is: %@.", resultString);
             }
         }];

        [testObject testMethod:@"Error string" withSemaphore:sem withCompletionBlock:^(NSString *blockString, NSError __autoreleasing *error)
         {
             NSLog(@"INSIDE SECOND BLOCK");

             resultString = blockString;

             if (!resultString)
             {
                 if (error)
                 {
                     NSLog(@"Error: %@", [error localizedDescription]);
                 }
                 else
                 {
                     NSLog(@"Error not recognized!");
                 }
             }
         }];

        NSLog(@"AFTER BLOCK!");

        while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW))
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    }

    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM