Wrapping asynchronous calls into a synchronous blocking thread?

Correct me if I am misunderstanding the constraints, but is the ultimate goal to keep the user from navigating away after sending an email? If so, then perhaps adding some sort of full screen progress overlay view as a subview of the window would be a good solution. Disable user interaction and remove the view when you get a success or failure callback via the delegate methods.


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.


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;
}