0

I have a method that I add to a GCD queue that I have created (so it's a serial queue) and then run it async. From within that block of code I make a dispatch to the main queue, when that block of code dispatched to the main queue is complete I set a BOOL flag to YES, so that I further down in my code can check if this condition is YES then I can continue to the next method. Here is the code in short:

dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);

dispatch_async(queue, ^{

        Singleton *s = [Singleton sharedInstance];

        dispatch_sync(dispatch_get_main_queue(), ^{
            [s processWithCompletionBlock:^{

                // Process is complete
                processComplete = YES;
            }];
        });
});

while (!processComplete) {

        NSLog(@"Waiting");
}

NSLog(@"Ready for next step");

However this does not work, because dispatch_sync is never able to run the code on the main queue. Is this because I'm running a while loop on the main queue (rendering it busy)?

However if I change the implementation of the while loop to this:

while (!processComplete) {

        NSLog(@"Waiting")
        NSDate *date = [NSDate distantFuture];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
}

It works without a glitch. Is this an acceptable solution for this scenario? Can I do it any other preferred way? What kind of magic stuff does NSRunLoop do? I need to understand this better.

Peter Warbo
  • 10,484
  • 12
  • 87
  • 186

3 Answers3

2

Part of the main thread's NSRunLoop job is to run any blocks queued on the main thread. By spinning in a while-loop, you're preventing the runloop from progressing, so the queued blocks are never run unless you explicitly make the loop run yourself.

Runloops are a fundemental part of Cocoa, and the documentation is pretty good, so I'd reccommend reading it.

As a rule, I'd avoid manually invoking the runloop as you're doing. You'll waste memory and make make things complicated very quickly if you have multiple manual invocations running on top of one another.

However, there is a much better way of doing this. Split your method into a -process and a -didProcess method. Start the async operation with your -process method, and when it completes, call -didProcess from the completion block. If you need to pass variables from one method to the other, you can pass them as arguments to your -didProcess method.

Eg:

dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);

dispatch_async(queue, ^{
        Singleton *s = [Singleton sharedInstance];

        dispatch_sync(dispatch_get_main_queue(), ^{
            [s processWithCompletionBlock:^{
                [self didProcess];
            }];
        });
});

You might also consider making your singleton own the dispatch queue and make it responsible for handling the dispatch_async stuff, as it'll save on all those nasty embedded blocks if you're always using it asynchronously.

Eg:

[[Singleton sharedInstance] processAyncWithCompletionBlock:^{
   NSLog(@"Ready for next step...");
   [self didProcess];
}];
Chris Devereux
  • 5,353
  • 1
  • 23
  • 30
  • So If I was not to make the callback from the main queue (main thread) I could use something like dispatch semaphores (http://stackoverflow.com/q/4326350/294661) or even use my solution (without the need to manually invoke the runloop) ? – Peter Warbo Dec 12 '12 at 10:53
  • Yes... although if you're on a serial queue, then any blocks submitted to that queue won't run until your block ends. Why do you need to block the thread at at all? Do you need to return a value from the calling method? – Chris Devereux Dec 13 '12 at 03:01
  • I need to wait for a process to complete before I can continue. No I don't need to return a value. – Peter Warbo Dec 13 '12 at 10:26
1

Doing something like what you posted will most likely freeze the UI. Rather than freezing up everything, call your "next step" code in a completion block.

Example:

dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_queue_t main = dispatch_get_main_queue();

dispatch_async(queue, ^{

        Singleton *s = [Singleton sharedInstance];

        dispatch_async(dispatch_get_main_queue(), ^{
            [s processWithCompletionBlock:^{
                // Next step code
            }];
        });
});
Bo A
  • 3,087
  • 1
  • 29
  • 48
  • 1
    Specifically, dispatch a block to do the work and, inside that block (when the work is completed) dispatch a block back to the main queue to update your UI. This will keep your app responsive (no beachball) while using multithreading as efficiently as possible. – Joshua Nozzi Dec 11 '12 at 16:52
  • Added an untested example. – Bo A Dec 11 '12 at 17:04
-2

Don't go creating a loop like that waiting for a value inside a block, variables in blocks are read only, instead call your completion code from inside the block.

dispatch_async(queue, ^{
    Singleton *s = [Singelton sharedInstance];
    [s processWithCompletionBlock:^{
        //process is complete
        dispatch_sync(dispatch_get_main_queue(), ^{
            //do something on main queue....
            NSLog(@"Ready for next step");
        });
    }];
});
NSLog(@"waiting");
jar_son
  • 107
  • 1
  • 3