I'm getting to know the NS/Objective-C model of concurrency. Say I have a command line tool that does something like this:
#include "myLibrary.h"
void callback(void* parameter){
cout<<"callback called.\n";
//some logic...
}
int main(int argc, char* argv[]){
myLibraryInit(callback);
std::string s;
while(true){
cin>>s;
myLibrarysResponseTo(s);
}
}
In my library, I'd like to be able to have two responses. One which starts a repeating timer and one which stops it. The timer should call the callback supplied to the library by myLibraryInit.
I've used NSTimers before in iPhone/iPad apps, and I think the problem stems from the different paradigm command line tools have. The main thread goes into main and never finishes it until the program is finished. This means it's not free to run the main run loop, which is what gets the timer going. I think. So how do I make an NSTimer work in this context?
The other thing is that Apple NSTimer documentation says I need to invalidate an NSTimer on the same thread it was installed. I don't know how to figure out what thread I was on when I installed the timer, and then keep track of it (and ensure it stays alive) until I want to invalidate the timer. I'm not sure if I'm just missing an obvious mapping between threads and dispatch queues, run loops, or something else. I am using core bluetooth and I initialize a central manager like so:
_centralManager=[[CBCentralManager alloc]
initWithDelegate: self
queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
];
so a callback may be triggered from here. If the callback includes some logic to call the library function that stops the timer, I can't guarantee from which thread came the invalidate. So how do I properly invalidate the timer?
I found this question but it doesn't allow a main to happen at the same time as the run loop that that the timer is on.
I hope I gave enough context. Thanks in advance for your replies.
You must call dispatch_main() or run an NSRunLoop in the main thread if any of the system frameworks [that use GCD or asynchronous operations] are to work correctly.
This can be as simple as calling [[NSRunLoop currentRunLoop] run]; at the end of your main() function (just make sure you schedule the kickoff work first as that method never returns).
Related
I created a simple singleton and run method in it:
- (void)run {
static int times = 0;
NSLog(#"times = %d", times++);
[self performSelector:#selector(run) withObject:nil afterDelay:MIN_DELAY];
}
But it doesn't work properly. It is executed only once.
But if I replace performSelector:withObject:afterDelay: with performSelector: then it will be called a lot of times (but I need a delay between calls).
So why method performSelector:withObject:afterDelay: doesn't work? And can I use this method at all?
Calls to -performSelector:withObject:afterDelay: require a run loop. Console applications do not, by default, pass control into the run loop ever. For more info, search for NSRunLoop.
From the docs:
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly.
You have no runloop. Ipso facto, this method does not perform correctly for you.
(Creating and starting a runloop is one of the things that calling UIApplicationMain does, but of course you are never calling it.)
I'm using NSOperationQueue, and NSOperation for running some function on background click.
But I want to be able, when user clicks some button, stop that Operation.
How can I do it?
Something like, [currentoperation stop];
Cancel - won't work me. I want to stop immediately.
Thanks
You should be calling the -cancel method, and the operation itself has to support being cancelled by monitoring the isCancelled property/keypath and safely stopping when its value becomes YES. If the NSOperation is your own, you will probably have to create a custom subclass to implement this functionality. You cannot (safely) force an arbitrary operation to immediately stop. It has to support being cancelled.
You can't stop immediately by using anything Apple provides with NSOperation. You can use -[cancel] as other people have suggested here, but the current operation will still run until completion. One way of getting close to use -[isCancelled] inside of your operation and sprinkle that throughout the code (especially in long running loops). Something like:
- (void)main {
// do a little work
if ([self isCancelled]) { return; }
// do a little more work
if ([self isCancelled]) { return; }
}
This way you'll get things stopped relatively soon.
If you're looking to really force the thread to stop, you may need to look into signal handling. There's a threaded example here. Sending a custom signal to a specific thread, you may be able to then terminate that thread in some way. This will be a lot more work, though, and is probably much more trouble than it's worth.
you use cancel, and test whether self (the NSOperation) has been cancelled during execution.
How would I go ahead about pausing a method without freezing the UI, and then continuing the process when told?
Thanks,
Kevin
There's not a way to interrupt execution of an arbitrary method and to then resume it later.
You can break your method into multiple pieces that represent the units of work that you want to be able to pause between. You can then use timers (either NSTimers or [NSObject performSelector:withObject:afterDelay:] for a specific pause interval.
More information on what you're trying to do might allow for some more specifically useful suggestions here.
There is a method to prevent the UI from stopping and eg. timers too. This is more or less the whole reason why we have run loops (well, thats not really true but in your case it is).
Just insert this into your function and everything will work:
while(pauseFunction)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1f]];
}
Remark: Everything scheduled with the runloop keeps running, this is the UI, timers, networking etc. pp.
The Problem
I have an NSOperationQueue called logEntryGeneratorQueue
I want to wait until all operations on the queue have completed
If I use:
[logEntryGeneratorQueue waitUntilAllOperationsAreFinished];
it works fine if the thread adding to the queue is in the background itself.
However, if I'm running this code via a unit test, it'll be running on the main thread. So I
came up with this "solution", which I really don't like:
if ([NSThread isMainThread]) {
while ([[logEntryGeneratorQueue operations] count] > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
} else {
[logEntryGeneratorQueue waitUntilAllOperationsAreFinished];
}
This was always less than ideal, but has always worked fine on 10.5. However, now I've upgraded my project to using the 10.6 SDK, and this breaks.
On one test, it actually quit the test before it completed. I've no idea why - I assume it's something to do with the way NSOperationQueues work differently in 10.6 - they now use GCD.
What I've Tried
I've tried replacing the runUntilDate with sleep, which, as I thought, means every test pauses forever when it gets here.
My Question
Is there a better way to wait for an NSOperationQueue to finish on a main thread? If not, how can I get this code working under 10.6?
The Solution
I realised that my code was in an eternal loop because I was calling mergeChangesFromContextDidSaveNotification on the main thread whilst also waiting for the queue to finish on the main thread. And since the merge changes was called after waitUntilAllOperationsAreFinished, it never got executed.
I think the answer is to change where I run NSOperationQueues from. I shouldn't run an NSOperationQueue that deals with core data stuff on the main thread. And I shouldn't really be running this intensive stuff on the main thread for performance reasons anyway I guess.
I'd say that waitUntilAllOperationsAreFinished should work as expected on 10.6, no matter from what thread it's called. Since operation queues in 10.6 no longer use the run loop, there is no point in not blocking and having the loop run. Did you try to just call waitUntilAllOperationsAreFinished=
I agree with Max: -waitUntilAllOperationsAreFinished should work. Is your queue -suspended ?
IMHO you need to consider the possibility that waitUntilAllOperationsAreFinished may hang if one (or all) of its operations are progressing using main app thread as a carrier. Example: your nsoperation is not concurrent and uses glkview auto-update loop for animation and updating own state and your operation is only done (and operation is marked finished) only if main thread has a chance to work. But it can't as it is blocked in waiting for finishing these operations.
I've run into an while trying to launch an NSTask from inside an NSOperation.
Here's a very simple app I've put together to showcase the problem. When you click the button, an NSOperation is queued. It sets up an NSRunLoop, and calls a method which invokes an NSTask. The task is really simple- it just launches /bin/sleep for two seconds (enough to easily see the spinner when things are working properly).
The app works as advertised, however if you change line 23 of TaskPerformer.m to an autorelease, (sorry, I'm a new poster so I can't link directly) or comment it out entirely (thus leaking the NSTask object), the NSOperation's thread will never exit. Its main runloop seems to be blocking on something.
Now, the problem here is twofold. First off, I don't understand why my thread is blocking, but moreover, if I turn on garbage collection for this application, the same behavior manifests itself. Because there's no way for me to manually release the NSTask, the thread blocks no matter what.
If someone could tell me what's going on, I'd be eternally grateful!
I see a couple different issues in the sample project you posted. In TaskPerformer.m you have:
[task waitUntilExit];
[task launch];
The waitUntilExit call is meant to be called after launching the task, and will simply block and do nothing until the task has completed. If you only care about waiting until the task has finished, and not about getting output from it or anything, then you should be able to just call launch followed by waitUntilExit, and not have to bother messing with the run loop at all.
If you do want to get output from the task though, then you'll want to get its standardOutput, which by default should return you an instance of NSFileHandle. You can then call readDataOfLength: or readDataToEndOfFile, both of which will block and return data from the process when data is available.
Since this entire thing is going to be done in a background thread anyway, it's OK that these methods block, but you wouldn't want to do the same thing on the main thread, since that would lock up the interface for the user until the task was complete. If you did this on the main thread, you'd want to use NSFileHandle's readInBackgroundAndNotify and friends. For a background thread though, using NSRunLoop shouldn't really be necessary.
I figured out what was going on here. Turns out my call to [runLoop run] was setting up the loop and running it indefinitely. It was never falling down into the while (!done) loop. Turns out that after calling run, an NSRunLoop will run until it has no more inputs. Calling release on my NSTask led to exactly this scenario, so (quite by accident) my runloop exited.
The solution was to remove [runLoop run] and simply rely on my own while loop. Hope this helps somebody else!