Wrapping asynchronous calls into a synchronous blocking thread?

12,205

Solution 1

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

Solution 2

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.

Solution 3

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.

Share:
12,205
valheru
Author by

valheru

Mobile Developer Check out my apps: iNacho: http://www.nachorater.com NY Resolutions: http://resolutionsapp.blogspot.com Pizza Splat: http://www.pizzasplat.com

Updated on June 12, 2022

Comments

  • valheru
    valheru almost 2 years

    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]];
    }
    
  • valheru
    valheru over 12 years
    Thanks for the response. It's actually a module that developers will use in their code so it should not have any real access to the UI...it's just meant to cause a short delay when the app first starts up.
  • Jeremy
    Jeremy over 12 years
    This is a good and widely used solution. @valheru you should consider NOT blocking the main thread under any circumstances as there could be a background process wishing to make changes on the main thread (such as a UI update, CoreData merging, notifications, .. list goes on).
  • Mark Adams
    Mark Adams over 12 years
    The OP isn't working in full control of the application, just a module related to sending email. Plus, if the guy signing the check wants it to block then its going to block. :)
  • valheru
    valheru over 12 years
    Yeah that seems to be more and more what I need to do and yet the code is distributed across several methods. I can tell that trying to move things around such that it all takes place synchronously is going to be a pain. It sounds like if I somehow take the runLoop work out of the equation, that would help. I guess I was hoping to simply wrap the block of code in a separate thread...then wouldn't the run loop be running in THAT thread and not the main thread? It doesn't seem to be the case though
  • valheru
    valheru over 12 years
    Thanks but as I posted as a comment to another possible answer, the module should not have any access to the UI itself because it will be included in separate apps. It's meant to be more of a background process in itself that simply starts up when the app starts up and when it's done, it gives full control back to the app.
  • valheru
    valheru over 12 years
    Thanks that makes sense. Like using mutexes. Is it supported in this form via iOS? Can the signal be called from a delegate handler? And finally, will it still run into the same problem with using the runLoop (see: the answer talking about SKPSMTPMessage's usage of runLoops). That is, will the wait() call block the runLoop that SKPSMTPMessage is using?
  • jscs
    jscs over 12 years
    If you create a new thread, the run loop is not automatically running; it has to be started manually. I think that putting the messaging in the background is the only possibility, though. See my update for a possible way to do that.
  • Dushi Fdz
    Dushi Fdz over 12 years
    It’s supported on iOS, and you can signal the semaphore from anywhere (as long as you have a reference to is), but unfortunately it looks like this will have the same problem as other solutions. Since dispatch_semaphore_wait() blocks the thread, it might not be the right solution.
  • valheru
    valheru over 12 years
    Thanks! So a form of this worked. All I really had to do was keep the runLoop running / processing (like you show here) while I wait for the delegates to return. =) Thanks so much!
  • gnasher729
    gnasher729 about 10 years
    Hopefully the OP will get another check for changing the code back when users start complaining. I assume it is an in-house application, because that kind of thing could get it refused on the app store.
  • gnasher729
    gnasher729 almost 9 years
    It's not only supported, this is the correct solution to the problem of producing a blocking version of an asynchronous call sequence. Much much better than faffing around with run loops, because this synchronous call can be used without any problems within other code, for example within a block in the background.