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.
Related
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).
Here is the problem.
I have a method called -(void)searchingInBackground which is running in background (performSelectorInBackground).
In this method, I have couple of different threads which are running in background too (performSelectorInBackground). Like this:
-(void)searchingInBackground
{
#autoreleasepool {
[self performSelectorInBackground:#selector(getDuplicatedPictures:) withObject:copyArray];
}
#autoreleasepool {
[self performSelectorInBackground:#selector(getLocationsOfPhotos:) withObject:copyArray];
}
... (and so on)
}
In each of functions in threads (ie. getDuplicatedPictures, getLocationsOfPhotos...) they will generate NSStrings at the end and I will use those strings to update my text field GUI.
In order to update my text field GUI. I created a function called UpdateGUI which will use to help me update all of my NSStrings. Like this,
-(void)UpdateUI
{
[_NumDupPhotosLabel(label for GUI) setStringValue: resultDupPhotos(string from thread function which is getDuplicatedPictures in this case)];
....(includes all of my strings from threads)
}
Here is the problem, when I call this UpdateGUI using performSelectorOnMainThread in each of threads function. It will give me EXC_BAD_ACCESS. Here is what I did.
For example:
-(void)getDupicatedPictures
{
resultDupPhotos = .....;
[self performSelectorOnMainThread:#selector(UpdateUI) withObject:nil waitUntilDone:YES];
}
If I do not use performSelectorOnMainThread, just set the values directly in those functions it works fine. I just want to better organize the code.
-(void)getDuplicatedPictures
{
resultDupPhotos = .....;
[_NumDupPhotosLabel setStringValue: resultDupPhotos]; (works good and it will set the value to the GUI label)
}
Could you guys tell me how to fix this? Thanks!!!
ARC or no?
if you have a crash, post the backtrace
surrounding a performInBackground:... call with an #autoreleasepool does nothing (NSAutoreleasePool isn't going to help, either -- you need the autorelease pool to be in the thread of execution)
if a variable is involved in a crash, show the variable's declaration and initialization
spawning a bunch of threads simultaneously to do a bunch of work is likely to be slower than doing the work sequentially. Concurrency should always be controlled. If you have a long running task, you might likely want to spin up a second thread. Or you might want to re-order operations. The issue, though, is that running multiple threads at once, especially if those threads are doing a lot of I/O, is just going to increase contention and may likely make things slower, often a lot slower.
More likely than not, one of the objects calculated on a background thread is being released before the main thread tries to use it. How do you ensure that resultDupPhotos is valid between threads?
-(void)GrabbingProcess:(void (^)())block;
{
AssertNonMainThread;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (self.OtherGrabbingIndicator == 0)
{
self.isStillLoading = true;
}
self.OtherGrabbingIndicator ++;
AssertMainThread
}];
block();
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateStatusAtList];
self.OtherGrabbingIndicator --;
if (self.OtherGrabbingIndicator ==0)
{
self.isStillLoading = false;
}
AssertMainThread
}];
}
Basically, the code is set up so that self.OtherGrabbingIndicator++ will always be balanced with self.OtherGrabbingIndicator--
I couldn't think of any reason why it would fail. It doesn't use to fail.
Now, once in a while it fails. self.OtherGrabbingIndicator would hover at 2 or 1 and all the threads have stopped. Somehow some ++ are not balanced by the --. But how can that be?
I have no idea how.
I checked around that there is no way self.OtherGrabbingIndicator is changed anywhere else.
One thing I plan to do is to add the value of a block in some array and remove those blocks appropriately. The problem is if I know the block how do I know what code that blocks represent?
It used to work fine and now it doesn't. This drives me nuts.
The only way I can think of is that somehow block() fails to complete but which block fails to complete and how can that happens?
If I press pause, all the threads are empty.
Update: Adding #synchronized() to the ++ and -- solves the problem. However, it DOESN'T make sense.
It should still work without #synchronized because mainQueue has only 1 maximum thread and execute things one at a time.
Unfortunately, I have no final answer for you. However, I did experience something similar recently with NSOperations and I will share it with you.
A few questions about your code:
Does the first block in addOperationWithBlock need to complete before the "block()" call ca be executed ?
Does the second block require both other operation to be completed before being executed ?
In my case, the operations where added in the proper order but based on the thread scheduling of the operation queue (which was lower than some other thread that needed the operation result) they were executed too late and made the code fail.
I do not know how time critical is your grabbing process, but in my case the use of NSOperation in a time critical part of my application was never working 100% of the time. Therefore, I redesigned to use an NSThread of which we could control the priority and since then everything works fine.
My conclusion after this experience is that NSOperation is a great tool because it is simple to use but is not of much help in time critical situation because it is not really meant for that use case.
Hope this helps, best of luck to you.
FACTS:
I have a method executing on a background thread :
[currentGame performSelectorInBackground:#selector(playOn:) withObject:self];
This method basically contains a while loop that keeps on executing until the user clicks on a Quit button:
-(void) playOn: (UIViewController*) thisViewController
{
while(!quitButtonPressed)
{
// this is my method's main loop
}
}
PROBLEM:
If the user clicks on Quit somewhere in the middle of the above loop
the rest of the loop would have to execute before it checks the BOOL once again and eventually stops. In order to prevent that from
happening and have the while-loop stop as soon as the user clicks on
Quit, I guess I could also add many if(quitButtonPressed) break;
here and there in my while loop in order to semi-constantly check and "immediately" break away if needed. However, this doesn't seem
very clever or practical from a design perspective given the size of
the above main while-loop and the fact that it contains many smaller
while-loops inside of it (the number of if.. break; I would have to
add would be quite big and could make things quite complicated to
figure out..)
POSSIBLE SOLUTION (but is it the right one?) :
So I was thinking that the best way would be to stop/cancel the
background thread on which the above method's while loop is executing,
instead of the while-loop itself, inside the method, the moment the
user clicks on Quit
Is this, or something similar (i.e. a better suggestion), possible and
how exactly could I do this?
POSSIBLE IMPLEMENTATION OF ABOVE SOLUTION:
I could create this new method:
-(void)checkQuitButton
{
while(!quitButtonPressed)
{
//wait
}
if(quitButtonPressed)
{
// stop this thread-->[currentGame performSelectorInBackground:#selector(playOn:) withObject:self];
// this is the method I'm looking for
}
}
And then I could start executing the above and my previous main method concurrently on two separate background threads as follows:
[currentGame performSelectorInBackground:#selector(playOn:) withObject:self];
[currentGame performSelectorInBackground:#selector(checkQuitButton) withObject:nil];
While the game while-loop is being executed another while-loop is checking the QuitButton at the same time. But is there a method that I can actually call in order to cancel what was started here:
[currentGame performSelectorInBackground:#selector(playOn:) withObject:self];
?
The correct solution is to periodically check for a "stop" flag. Abruptly terminating a thread provides no opportunity to clean up resources. In short, you would leak memory terribly.
But the deeper issue is that you almost certainly should not have this kind of thread. It strongly suggests an incorrect design. In iOS, most background operations should take the form of focused operations, implemented either with NSOperation or blocks with Grand Central Dispatch. You should very, very seldom need a long lived thread that is performing many different kinds of functions. Within your operation, it should be fairly straightforward where to put the "check for cancel" statements.
There is also almost no case where you should use performSelectorInBackground:. It is an incredibly dangerous method that gives you very little control. Instead, read the Concurrency Programming Guide for guidance on how to properly implement background operations. Pay special attention to the section "Migrating Away From Threads."
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.