Wrapping asynchronous calls into a synchronous blocking thread? - objective-c

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

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

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.

while( ![messageDelegate hasFinishedOrFailed] ){
// Allow the run loop to do some processing of the stream
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
this will cause table view can not scroll sometimes, and the alert view can not dismiss

Related

how to receive NSWorkspace and accessibility notifications on another thread

I am trying to do window management, but I need the code running on a separate thread.
The first thing I need to do is subscribe to app notifications like this:
NSNotificationCenter *nc = [[NSWorkspace sharedWorkspace] notificationCenter];
NSString *not = NSWorkspaceDidLaunchApplicationNotification;
[nc addObserver:self selector:#selector(appLaunched:) name:not object:nil];
But if I simply call addObserver on another thread, will the notifications be delivered there instead?
Apple has this reference, but it seems overcomplicated:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Notifications/Articles/Threading.html
If the answer to the first question is no, then why couldn't I just forward the message like this?
NSThread *other;
- (void)appLaunched:(NSNotification*)not {
if([NSThread currentThread] != otherThread)
[self performSelector:#selector(appLaunched:) onThread:other withObject:not waitUntilDone:NO];
else
// do respond to notification
}
The second thing I need to do is add an AXObserver to a runloop on the other thread.
If I call CFRunLoopGetCurrent() from another thread, will a run loop automatically be created like calling [NSRunLoop currentRunLoop] or do a I have to create one?
Observers which are registered using -addObserver:selector:name:object: receive the notification on the thread where it's posted, not where they registered. There's also -addObserverForName:object:queue:usingBlock:, which causes the notification to be received on the specified queue, but that doesn't let you make it arrive on a specified background thread. (Only the main queue is tied to a thread.)
You can shunt a notification to another thread in the manner you suggest. However, the original receiving thread has to be idling to receive the notification in the first place. Or, rather, it has to be idling in order to allow NSWorkspace to detect the condition which causes it to post the notification.
All threads create a runloop for themselves as soon as it's requested. It's basically impossible to observe a thread not having a runloop, so you might as well just act as though the runloop is created when the thread is created.
All of that said, your original goal – "I am trying to do window management, but I need the code running on a separate thread" – is problematic. Many GUI manipulations are not legal from background threads. Also, why do you "need" to do it from a background thread? And if your main thread is not free, you're not going to receive the workspace notifications in the first place.

Better way to Trigger Asynchronous Callbacks in Objective-C

I am looking for a better way to do this, if possible.
I have an asynchronous callback that updates a local sqlite database. I set a flag in a singleton variable (archiveUpdateComplete) when the update completes. I sleep in a do-while until the flag gets set to true, then I hydrate my tableview. Would like to remove sleep()! Thanks for any suggestions.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
- (void)viewDidLoad
{
dispatch_async(kBgQueue, ^{
//Hydrate word archive table view
do {
sleep(1.0);
} while ([sharedManager archiveUpdateComplete]==NO);
[self performSelectorOnMainThread:#selector(hydrateWordArchive) withObject:nil waitUntilDone:YES];
//Run custom activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
}
If you need to poll
Polling/sleeping is rarely necessary or good. As an alternative:
You can attach an NSTimer to the main thread's run loop.
The selector the timer calls can test [sharedManager archiveUpdateComplete]
if YES is returned, then
invalidate the timer
call [MBProgressHUD hideHUDForView:self.view animated:YES];
If you don't need to poll
There are a few immediate alternatives. Which you choose depends on what knows about what:
If your manager knows who to message following completion, then the manager can simply message it. If that must occur on the main thread you can use -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] to forward to the main thread. You may also see this approach with delegates. In the case of a singleton, it doesn't make a lot of sense to take this route.
If your manager does not know who is interested in the change/completion, your manager can post a NSNotification after the task has finished (on the current thread or from the main thread).
Key Value Observing (KVO) is another option.
Perhaps I'm missing something, but why don't you just use a completion callback for this?
In other words, you change your computation to "think" in terms of nested blocks. The first async block (on some concurrent queue) does the work of updating the database, and when it's done it dispatches another async block (to the same concurrent queue) which hydrates the tableview. Finally, from that block you dispatch_async yet another block on the main queue which updates the UI, since that's the only bit that needs to execute on the main queue.
Rather than poll, in other words, you want to chain your async operations. See COMPLETION CALLBACKS section of the man page for dispatch_async().

What's the difference between performSelectorOnMainThread: and dispatch_async() on main queue?

I was having problems modifying a view inside a thread. I tried to add a subview but it took around 6 or more seconds to display. I finally got it working, but I don't know how exactly. So I was wondering why it worked and what's the difference between the following methods:
This worked -added the view instantly:
dispatch_async(dispatch_get_main_queue(), ^{
//some UI methods ej
[view addSubview: otherView];
}
This took around 6 or more seconds to display:
[viewController performSelectorOnMainThread:#selector(methodThatAddsSubview:) withObject:otherView
waitUntilDone:NO];
NSNotification methods - took also around 6 seconds to display the observer was in the viewController I wanted to modify paired to a method to add a subview.
[[NSNotificationCenter defaultCenter] postNotificationName:
#"notification-identifier" object:object];
For reference these were called inside this CompletionHandler of the class ACAccountStore.
accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
// my methods were here
}
}
By default, -performSelectorOnMainThread:withObject:waitUntilDone: only schedules the selector to run in the default run loop mode. If the run loop is in another mode (e.g. the tracking mode), it won't run until the run loop switches back to the default mode. You can get around this with the variant -performSelectorOnMainThread:withObject:waitUntilDone:modes: (by passing all the modes you want it to run in).
On the other hand, dispatch_async(dispatch_get_main_queue(), ^{ ... }) will run the block as soon as the main run loop returns control flow back to the event loop. It doesn't care about modes. So if you don't want to care about modes either, dispatch_async() may be the better way to go.
It's likely because performSelectorOnMainThread:withObject:waitUntilDone: queues the message with common run loop modes. According to Apple's Concurrency Programming Guide, the main queue will interleave queued tasks with other events from the app's run loop. Thus, if there are other events to be processed in the event queue, the queued blocks in the dispatch queue may be run first, even though they were submitted later.
This article is a superb explanation to performSelectorOnMainThread vs. dispatch_async, which also answers the above question.
Did you try thePerformSelectorOnMainThread with waitUntilDone=YES
Eg:
Code:
[viewController performSelectorOnMainThread:#selector(methodThatAddsSubview:) withObject:otherView waitUntilDone:YES];
I think that might solve the issue as of why the PerformSelectorOnMainThread takes so long to respond.

Only call function if another function is ready

How can i make sure in Objective-C, that a function only get called, until another function is ready?
Update:
-(void)reloadJsonFromServer {
[[Sync sharedObject] synchronise];
[self reload];
}
I've got this function.
The second function "reload" should only be called, if the first function is – in this case it's a singlton – is ready. Ready means, that the first function is no more longer running.
So you want to wait on the completion of an asynchronous method? There's a whole bunch of ways to do that.
Make the synchronise method itself call reload on your object when it finishes
dispatch_async the reload method and have it just wait until the other method populates some flag or data structure that you are waiting on before continuing (BOOL synchronised or similar). Note that if your reload method does anything with UIKit, though, then you need to run it on the main thread.
Change the way synchronise runs so it doesn't actually return to the caller until it's done synchronising, but then dispatch_async the reloadJsonFromServer method.
Change synchronise as in the third point, but instead of using dispatch_async, add both of the method calls to an NSOperationQueue as NSOperations, with reload dependent on the completion of synchronise. The operation queue will handle it after that.
Those are just a few, I'm sure other people can suggest more.
In the last few days, i've learnd something about notifications.
I think that is a good way too, to handle something like this. For more information about this look at this blog entry.
Custom Events in Objective-C
A bit late, but with NSNotification it can be handled.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserverForName:nil
object:nil
queue:nil
usingBlock:^(NSNotification *notification)
{
NSLog(#"%#", notification.name);
}];
Look at this: http://nshipster.com/nsnotification-and-nsnotificationcenter/

Best way to make NSRunLoop wait for a flag to be set?

In the Apple documentation for NSRunLoop there is sample code demonstrating suspending execution while waiting for a flag to be set by something else.
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
I have been using this and it works but in investigating a performance issue I tracked it down to this piece of code. I use almost exactly the same piece of code (just the name of the flag is different :) and if I put a NSLog on the line after the flag is being set (in another method) and then a line after the while() there is a seemingly random wait between the two log statements of several seconds.
The delay does not seem to be different on slower or faster machines but does vary from run to run being at least a couple of seconds and up to 10 seconds.
I have worked around this issue with the following code but it does not seem right that the original code doesn't work.
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
using this code, the log statements when setting the flag and after the while loop are now consistently less than 0.1 seconds apart.
Anyone any ideas why the original code exhibits this behaviour?
Runloops can be a bit of a magic box where stuff just happens.
Basically you're telling the runloop to go process some events and then return. OR return if it doesn't process any events before the timeout is hit.
With 0.1 second timeout, you're htting the timeout more often than not. The runloop fires, doesn't process any events and returns in 0.1 of second. Occasionally it'll get a chance to process an event.
With your distantFuture timeout, the runloop will wait foreever until it processes an event. So when it returns to you, it has just processed an event of some kind.
A short timeout value will consume considerably more CPU than the infinite timeout but there are good reasons for using a short timeout, for example if you want to terminate the process/thread the runloop is running in. You'll probably want the runloop to notice that a flag has changed and that it needs to bail out ASAP.
You might want to play around with runloop observers so you can see exactly what the runloop is doing.
See this Apple doc for more information.
Okay, I explained you the problem, here's a possible solution:
#implementation MyWindowController
volatile BOOL pageStillLoading;
- (void) runInBackground:(id)arg
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Simmulate web page loading
sleep(5);
// This will not wake up the runloop on main thread!
pageStillLoading = NO;
// Wake up the main thread from the runloop
[self performSelectorOnMainThread:#selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void) wakeUpMainThreadRunloop:(id)arg
{
// This method is executed on main thread!
// It doesn't need to do anything actually, just having it run will
// make sure the main thread stops running the runloop
}
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}
#end
start displays a progress indicator and captures the main thread in an internal runloop. It will stay there till the other thread announces that it is done. To wake up the main thread, it will make it process a function with no purpose other than waking the main thread up.
This is just one way how you can do it. A notification being posted and processed on main thread might be preferable (also other threads could register for it), but the solution above is the simplest I can think of. BTW it is not really thread-safe. To really be thread-safe, every access to the boolean needs to be locked by a NSLock object from either thread (using such a lock also makes "volatile" obsolete, as variables protected by a lock are implicit volatile according to POSIX standard; the C standard however doesn't know about locks, so here only volatile can guarantee this code to work; GCC doesn't need volatile to be set for a variable protected by locks).
In general, if you are processing events yourself in a loop, you're Doing It Wrong. It can cause a ton of messy problems, in my experience.
If you want to run modally -- for example, showing a progress panel -- run modally! Go ahead and use the NSApplication methods, run modally for the progress sheet, then stop the modal when the load is done. See the Apple documentation, for example http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html .
If you just want a view to be up for the duration of your load, but you don't want it to be modal (eg, you want other views to be able to respond to events), then you should do something much simpler. For instance, you could do this:
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:#selector(runInBackground:) toTarget:self withObject:nil];
[progress setHidden:NO];
}
- (void)wakeUpMainThreadRunloop:(id)arg
{
[progress setHidden:YES];
}
And you're done. No need to keep control of the run loop!
-Wil
If you want to be able to set your flag variable and have the run loop immediately notice, just use -[NSRunLoop performSelector:target:argument:order:modes: to ask the run loop to invoke the method that sets the flag to false. This will cause your run loop to spin immediately, the method to be invoked, and then the flag will be checked.
At your code the current thread will check for the variable to have changed every 0.1 seconds. In the Apple code example, changing the variable will not have any effect. The runloop will run till it processes some event. If the value of webViewIsLoading has changed, no event is generated automatically, thus it will stay in the loop, why would it break out of it? It will stay there, till it gets some other event to process, then it will break out of it. This may happen in 1, 3, 5, 10 or even 20 seconds. And until that happens, it will not break out of the runloop and thus it won't notice that this variable has changed. IOW the Apple code you quoted is indeterministic. This example will only work if the value change of webViewIsLoading also creates an event that causes the runloop to wake up and this seems not to be the case (or at least not always).
I think you should re-think the problem. Since your variable is named webViewIsLoading, do you wait for a webpage to be loaded? Are you using Webkit for that? I doubt you need such a variable at all, nor any of the code you have posted. Instead you should code your app asynchronously. You should start the "web page load process" and then go back to the main loop and as soon as the page finished loading, you should asynchronously post a notification that is processed within the main thread and runs the code that should run as soon as loading has finished.
I’ve had similar issues while trying to manage NSRunLoops. The discussion for runMode:beforeDate: on the class references page says:
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it returns after either the first input source is processed or limitDate is reached. Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. Mac OS X may install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
My best guess is that an input source is attached to your NSRunLoop, perhaps by OS X itself, and that runMode:beforeDate: is blocking until that input source either has some input processed, or is removed. In your case it was taking "couple of seconds and up to 10 seconds" for this to happen, at which point runMode:beforeDate: would return with a boolean, the while() would run again, it would detect that shouldKeepRunning has been set to NO, and the loop would terminate.
With your refinement the runMode:beforeDate: will return within 0.1 seconds, regardless of whether or not it has attached input sources or has processed any input. It's an educated guess (I'm not an expert on the run loop internals), but think your refinement is the right way to handle the situation.
Your second example just work around as you poll to check input of the run loop within time interval 0.1.
Occasionally I find a solution for your first example:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]);