Search code examples
iosmultithreadingffmpegnsoperationqueue

NSOperationQueue's threads just don't die


Sorry, it's a bit wordy, but I wanted to make sure I was clear! ;-)

I have an iOS app that uses FFMPEG for streaming RTSP. I've multi-threaded FFMPEG using NSOperationQueue such that most its work, other than painting the image to the screen, of course, happens in background threads. Works great! ...except for the fact that threads the NSOperationQueue creates never die!

I init the Queue in the class' init method with:

self->opQ = [[NSOperationQueue alloc] init];
[self->opQ setMaxConcurrentOperationCount:1];

I add methods to the Queue using blocks:

[self->opQ addOperationWithBlock:^{
        [self haveConnectedSuccessfullyOperation];
}];

Or

[self->opQ addOperationWithBlock:^{
        if (SOME_CONDITION) {
            [self performSelectorOnMainThread:@selector(DO_SOME_CRAP) withObject:nil waitUntilDone:NO];
        }
    }];

Later, when I need to tear down the RTSP stream, in addition to telling FFMPEG to shut down, I call:

[self->opQ cancelAllOperations];

Which does indeed stop the threads from doing any work , but never actually destroys them. Below, you'll see a screen shot of threads that are doing nothing at all. This is what my threads look like after starting/stoping FFMPEG several times.

Bad threads

I seem to remember reading in Apple's documentation that NSOperations and the threads they are run on are destroyed once they are done executing, unless otherwise referenced. This doesn't appear to be the case.

Do I just need to destroy the NSOperationQueue, then re-init it when I need to start up FFMPEG again (I just realized I haven't tried this)? Anyone know how I need to kill these extra threads?

THANKS!


Solution

  • I solved it by creating NSBlockOperations so that I could monitor the isCancelled state, while also making the new NSBlockOperations' content more intelligent, such that I simplified the routine that would add the operations to the queue.

    ... Plus, I made an NSOperationQueue n00b mistake: I was adding operations to the queue on a looping basis, which fired up to 30 times per second (matching the video's frame rate). Now, however, the operation is added to the queue only once and the looping behavior is contained within the operation instead of having the loop add the operation to the queue.

    Previously, I had something like this (pseudo code, since I don't have the project with me):

    NSTimer *frameRateTimeout = [NSTimer scheduledTimerWithTimeInterval:1/DESIRED_FRAMES_PER_SECOND target:self selector:@selector(ADD_OPERATION_TO_QUEUE_METHOD:) userInfo:nil repeats:YES];
    
    -(void)ADD_OPERATION_TO_QUEUE_METHOD:(NSTimer *)timer {
       [opQ addOperation:displayFrame];
    }
    

    Which worked well, as the OS would correctly manage the queue, but it was not very efficient, and kept those threads alive forever.

    Now, it's more like:

    -(id)init {
       self = [super init];
       if (self) {
          // alloc/init operation queue
          ...
          // alloc/init 'displayFrame' 
          displayFrame = [NSBlockOperation blockOperationWithBlock:^{
             while (SOME_CONDITION && ![displayFrame isCancelled]) {
                if (playVideo) {
                   // DO STUFF 
                   [NSThread sleepForTimeInterval:FRAME_RATE];
                } 
                else {  // teardown stream
                   // DO STUFF
                   break;
                }
             }
          }]; 
       }
    
       return self;
    }
    
    - (void)Some_method_called_after_getting_video_ready_to_play {
       [opQ addOperation:displayFrame];
    }
    

    Thanks, Jacob Relkin, for responding to my post.
    If anyone needs further clarification, let me know, and I'll post better code once I have the project in my hands again.