Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping asynchronous calls into a synchronous blocking thread?

I'm writing an iOS module which currently sends an email asynchronously (using delegates). It uses SKPSMTPMessage which works great. My problem is the customer wants the code to fully block the thread until after the email has been sent (or failed to be sent). So they are basically asking for a synchronous solution, when currently it will attempt to send the email and then return from that block of code before the email has been sent.

So instead of trying to rewrite the SKPSMTPMessage code in a synchronous way (there doesn't seem to be any synchronous options for it), I am hoping to find some way of wrapping that block of asynchronous code in its own thread and maybe make the main thread wait for it to fully end (delegates and all).

I've tried a few different methods using NSOperations and NSThread but maybe I'm not doing something right because everytime I try to block the main thread, the asynchronous delegate calls still never seem to finish (do they come back on the main thread or something?).

Any information or even other ideas appreciated.

PS ~ I realize that this is a bit backwards. In most cases, asynchronous seems to be the way to go but this is a special case and the customer has their reasons for wanting it.

EDIT: Thanks for all the input. As suggested by one of the answers, I ended up just using a while loop that waited for the delegates to return yet let the runLoop continue as well like so:

while( ![messageDelegate hasFinishedOrFailed] ){
    // Allow the run loop to do some processing of the stream
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
like image 302
valheru Avatar asked Dec 20 '11 22:12

valheru


2 Answers

I would try using dispatch semaphores. From the man page for dispatch_semaphore_create(3):

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    foo();
    dispatch_semaphore_signal(sema);
});

bar();

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
sema = NULL;

The call to dispatch_semaphore_wait() will block until the call to dispatch_semaphore_signal() completes.

like image 97
Jeff Kelley Avatar answered Sep 28 '22 01:09

Jeff Kelley


I don't believe there's any way to do exactly this without modifying SKPSMTPMessage. The class isn't actually using separate threads; instead it's using an NSStream in concert with the thread's run loop to avoid blocking:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                            forMode:NSRunLoopCommonModes];

The stream is acting as an input source for the run loop; the run loop is then free to process other events until something happens with the stream, at which time the stream notifies its delegate. Everything is still happening on the original (main) thread; if you try to block it yourself, you will also block the run loop and it won't be able to do anything with the stream.

Others have already pointed out that blocking the main thread is a bad idea; aside from UX issues, the system may terminate any app that doesn't respond to events for too long a period. That said, you can put the whole message setup into the background, giving it its own run loop for the stream to work in, and block the main thread, using GCD. Unfortunately I can't think of a way for the delegate to signal that it's done without polling, though.

dispatch_queue_t messageQueue;
messageQueue = dispatch_queue_create("com.valheru.messageQueue", NULL);

// dispatch_sync blocks the thread on which it's called
dispatch_sync(messageQueue, ^{
    [messageManager tryToDeliverMessage];
});
dispatch_release(messageQueue);

Where tryToDeliverMessage looks something like:

- (void) tryToDeliverMessage {
    // Create the message and let it run...

    // Poll a flag on the delegate
    while( ![messageDelegate hasFinishedOrFailed] ){
        // Allow the run loop to do some processing of the stream
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }

    return;
}
like image 22
jscs Avatar answered Sep 28 '22 01:09

jscs