12

Is there an equivalent to [NSOperationQueue currentQueue] or [NSThread currentThread] for NSOperation?

I have a fairly complex domain model where the heavy processing happens quite deep down in the call stack. In order to timely cancel an operation I would need to pass the NSOperation as a parameter to every method until I get to the point where I want to interrupt a longer running loop. Using threads I could use [[NSThread currentThread] isCancelled] so it would seem convenient if there is an equivalent for NSOperation, unfortunately there is only the seemingly useless [NSOperationQueue currentQueue].

gcamp
  • 14,622
  • 4
  • 54
  • 85
Jon Tirsen
  • 4,750
  • 4
  • 29
  • 27

6 Answers6

7

Came up with an extension in swift that returns the running operations

extension NSOperationQueue {
    public var runningOperations: [NSOperation] {
        return operations.filter {$0.executing && !$0.finished && !$0.cancelled}
    }
}

You can then pick up the first one

if let operation = aQueue.runningOperations.first {}
Arsonik
  • 2,276
  • 1
  • 16
  • 24
  • Sneaky, but I gave it a try and it works quite elegantly, including across classes from the Swift declaration to being called in Objective-C using [[[NSOperationQueue currentQueue] runningOperations] objectAtIndex:0]. – eGanges Jun 16 '16 at 19:52
  • @JonTirsen I'd be careful here. According to the docs for the `operations` property "[t]he array in this property contains zero or more `Operation` objects in the order in which they were added to the queue. **This order does not necessarily reflect the order in which those operations will be executed.**" I am currently looking for a way to get currently executing operations too :) – Left as an exercise Jul 13 '21 at 02:10
4

No, there's no method to find the currently executing operation.

Two ways to solve your problem:

  1. Operations are objects. If you need object A to talk to object B, you'll need to arrange for A to have a reference to B. There are lots of ways to do that. One way is to pass the operation along to each object that needs to know about it. Another is to use delegation. A third is to make the operation part of some larger "context" that's passed along to each method or function. If you find that you need to pass a reference from one object through several others just to get it to the object that will finally use it, that's a clue that you should think about rearranging your code.

  2. Have the "heavy lifting" method return some value that gets passed up the call chain. You don't necessarily need the heavy lifting method to call [currentOperation cancel] to accomplish your goal. In fact, it would be better to have it return some value that the operation will understand to mean "work is done, stop now" because it can check that return value and exit immediately rather than having to call -isCancelled once in a while to find out whether it has been cancelled.

Caleb
  • 124,013
  • 19
  • 183
  • 272
3

This isn't a good idea. Operations are usually canceled by their queue. Within the operation's main() method, you can periodically check if self is cancelled (say, every n trips through a loop, or at the start of every major block of commands) and abort if so.

To respond to a cancellation (say, some UI element tied to the operation's or queue's status), you use key value observing (KVO) to have your controller observe the operations' started, completion, and cancelled properties (as needed), then set your UI's state (always on the main thread) when those keys are updated. Per JeremyP's comments, it's important to note the KVO notifications come from the op's thread and UI should (almost) always be manipulated on the main thread, so you'll need to use -performSelectorOnMainThread... methods to update your actual UI when you receive a state change KVO note about your operations.

What are you really trying to do? That is, why do you feel other parts of your app need to know directly about the current operation?

Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
  • -1 I'm afraid. None of this is going to work. Setting an operation's isCancelled property will have no effect unless the operation periodically checks the isCancelled property. Certainly KVO won't work because KVO notifications are received on the same thread they are sent on. – JeremyP Sep 07 '11 at 16:10
  • 3
    Errr ... did you read my post before down-voting it? Observing these properties is *exactly* how these things are done. You observe the state change then use -performSelectorOnMainThread... to do anything that needs doing on the main thread. "None of this is going to work" is news to me since I have several apps doing just that. Also, I never said you *set* the cancellation. In fact I specifically said the queue cancels the operation and the operation checks this state periodically and stops working if true. – Joshua Nozzi Sep 07 '11 at 17:18
  • What happens is: queue cancels ops -> op posts KVO note of isCancelled, handles stopping itself -> Observer (some controller of yours) notes change, and if has work to do, does it on main thread. Works every time. Just because your controller is messaged on another thread doesn't mean it can't handle it. If it has to do something on the main thread, go right ahead and send itself something on the main thread. – Joshua Nozzi Sep 07 '11 at 17:22
  • That said, I can see one of my sentences that could stand to be clarified. – Joshua Nozzi Sep 07 '11 at 17:31
  • I have a fairly complex domain model where the heavy processing happens quite deep down in the call graph. In order to timely cancel an operation I would need to pass the NSOperation as a parameter to every method of the call graph. Using threads I could instead just use [NSThread currentThread] so it would seem convenient if there is an equivalent for NSOperation, unfortunately there is only the seemingly useless [NSOperationQueue currentQueue]. – Jon Tirsen Sep 08 '11 at 10:38
  • 1
    @JonTirsen, methods "deep down in the call graph" probably shouldn't assume that they're even being called as part of an operation. Or, if they can know that, the operation should likely be something that's known to their respective objects. Either way, the farther you are from the operation when you decide to cancel it, the more likely it is that you should refactor your code. – Caleb Sep 08 '11 at 13:49
  • +1 for Caleb's comment. Something seems to be amiss in the design if you're having trouble. Perhaps what you really mean to do is "exit the operation's main after setting -isSuccessful to NO". (the original question seems to have been heavily rewritten by another -- please verify, OP, this is still your intended question) – Joshua Nozzi Sep 08 '11 at 14:28
  • @Joshua Nozzi: That's much clearer now, so I've removed the down vote. The point I was making is that the operation's execution will only stop when the operation checks the `isCancelled` flag. No amount of KVO magic will make an operation stop that's running in a loop never checking `isCancelled`. – JeremyP Sep 14 '11 at 09:24
1

You can use a combination of [NSOperationQueue currentQueue] & [NSThread currentThread] to accomplish this.

Essentially, you need to loop through the operations on the currentQueue and find the operation running on the currentThread.

NSOperation doesn't provide access to the thread it is running on, so you need to add that property yourself and assign it.

You're probably already subclassing NSOperation and providing a main, so add a 'thread' property to that subclass:

@interface MyOperation : NSOperation
    @property(nonatomic,strong) NSThread *thread ;
@end

Then, in your 'main' assign the current thread to that property

myOperation.thread = [NSThread currentThread]

You can then add a 'currentOperation' method:

+(MyOperation *)currentOperation
{
    NSOperationQueue *opQueue = [NSOperationQueue currentQueue] ;
    NSThread *currentThread = [NSThread currentThread] ;

    for( MyOperation *op in opQueue.operations ) {
        if( [op isExecuting] && [op respondsToSelector:@selector(thread)] ) {
                if( op.thread == currentThread ) {
                    return ( op ) ;
                }
            }
        }
    }

    return nil ;
}
Rich Waters
  • 1,567
  • 16
  • 15
1

You could store the current operation in the thread dictionary. Just remember to get rid of it before you exit. You can safely use the thread dict if you created the object.

Tony
  • 36,591
  • 10
  • 48
  • 83
0

How do you know which operation you want to cancel? When you get to the point that you want to cancel, just call [myQueue operations] and go through the operations until you find ones that you now want to cancel. I guess if you have millions of operations (or thousands) this might not work.

[myQueue operations] is thread safe - a snapshot of the Queue contents. You can dive through it pretty quick cancelling at will.

Another way: NSOperationQueue is not a singleton, so you can create a Q that has say 200 jobs on it, and then cancel all 20 by just getting that Q and cancelling them all. Store the Q's in a dictionary on the main thread, and then you can get the jobs you want canceled from the dict and cancel them all. i.e. you have 1000 kinds of operations and at the point in the code where you realize you don't need a certain task, you just get the Q for that kind, and look through it for jobs to cancel.

Tom Andersen
  • 7,132
  • 3
  • 38
  • 55