Wait for a timer or a condition to become true and then process the code? (Wait for a iOS camera to adjust focus)

11,724

Solution 1

Since you are not in control of AVCaptureStillImageOutput's isAdjustingFocus (you are not the one setting it to true or false) then you can't use my previous answer (that's what I meant by I would need the exact situation: what are we waiting for, and why. Implementation details depend on these informations).

IMHO, the best option would indeed be to implement some timeout and wait for it just as you suggested. Be sure to use usleep() so you aren't polling continuously.

NSDate* date = [NSDate date];
while (TRUE)
{

   if (myBOOL)
   {
       // the condition is reached
       break;
   }

   if ([date timeIntervalSinceNow] < -5)
   {
    // the condition is not reached before timeout
    break;
   }

   // adapt this value in microseconds.
   usleep(10000);
}

Solution 2

You might consider an NSConditionLock on which you can lockWhenCondition:beforeDate:, or possibly schedule something to occur in five seconds (eg, by NSTimer or dispatch_after) that checks whether the other processing has already begun, the other processing being event triggered and setting a flag.

EDIT:

So, for the record:

const NSInteger kConditionLockWaiting = 0;
const NSInteger kConditionLockShouldProceed = 1;

[...]

conditionLock = [[NSConditionLock alloc] initWithCondition:kConditionLockWaiting];

[...]

dispatch_async(...
^{
    [conditionLock
         lockWhenCondition:kConditionLockShouldProceed
         beforeDate:[[NSDate date] dateByAddingTimeInterval:5.0]];

    // check the return condition to find out whether you timed out
    // or acquired the lock
});

[...]

- (void)observeValueForKeyPath:(NSString *)keyPath
        ofObject:(id)object change:(NSDictionary *)change
        context:(void *)context
{
    if(this is the appropriate property)
    {
        [conditionLock lock];
        [conditionLock unlockWithCondition:kConditionLockShouldProceed];
    }
}

Solution 3

There are a handful of ways to do what you're after at various levels of abstraction, using NSLock, dispatch barriers, NSOperation dependencies and whatnot. But since you're using GCD already, the dispatch_semaphore_* functions will do what you need simply.

Something like (off the top of my head, may have typos:)

  // this is the barrier one task will use to signal others that it's done.
  dispatch_semaphore_t mySemaphore = dispatch_semaphore_create(0);
  __block NSString *result = @"not done yet";
  // adjust these to change which task "wins":
  int hardTaskLength = 3;
  int timeoutSeconds = 5;

  // this is the first task:
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    // do something slow and difficult
    sleep(hardTaskLength);
    result = @"now I am done";
    // then when we're done, let the world know:
    dispatch_semaphore_signal(mySemaphore);
  });

  // and this is the second, dependent one:
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutSeconds * NSEC_PER_SEC));
    // wait for the first task to complete, or give up after a bit:
    BOOL timedOut = dispatch_semaphore_wait(mySemaphore, timeout);
    // now do stuff that wants the result of the first task
    if (timedOut)
      NSLog(@"Gave up waiting after %d seconds, result is: %@", timeoutSeconds, result);
    else
      NSLog(@"Processing finished; result is: %@", result);

    // these can happen wherever is appropriate, after everything is done:
    dispatch_release(mySemaphore);
    mySemaphore = 0;
  });

The manual section "Grand Central Dispatch reference" has more information on how semaphores work. A semaphore is essentially a thread-safe counter; "signal"ing it increases it by one, and "wait"ing it decreases it by one... unless the counter is zero; then the "wait" stops and waits until something else increases the counter again, then decrements and continues.

From the docs: "Passing zero for the value [in dispatch semaphore_create] is useful for when two threads need to reconcile the completion of a particular event." Which is exactly what you're doing.

[edit to add]:

Based on the additional info in the question, though, dispatch queues look to be overkill for what you're doing. Everything UIKit has to happen on the main thread anyway, so do something like this:

  • At the beginning of the 5 seconds, set up KVO on the property you care about, AND start a 5 second NSTimer.
  • In the KVO listener, take the picture, and cancel the timer.
  • In the timer handler method, take the picture, and unregister the KVO observation.
  • If something exceptional happens first (a cancel button?) and you need to abort, both unregister KVO cancel the timer.

Since everything happens on the main thread there's no worry about contention, and the timer handles keeping the main thread from blocking.

Solution 4

I would use -[NSObject performSelector:withObject:afterDelay:] to take care of the timeout part. If the KVO change notification fires before the timeout, just use +[NSObject cancelPreviousPerformRequestsWithTarget:selector:object:] to cancel the timeout.

Don't block waiting for either thing to happen (as with your while loop). Just return to the event loop. It will call out to your code when either thing happens.

Share:
11,724
dreamzor
Author by

dreamzor

(my about me is currently blank)

Updated on August 10, 2022

Comments

  • dreamzor
    dreamzor over 1 year

    I want to process some code after some condition checking. First of it is that some variable must be true (I have a Key-Value Observer assigned to it). Second - if that variable hasn't become true for some time (e.g. 5 seconds), then nevermind the variable and just process the code.

    I come with an obvious solution, which, I think, is bad: infinite while() loop in another dispatch queue, every time checking the true condition and time passed. And all this code is wrapped in another dispatch queue... well, does not look good for me.

    The draft pseudocode of what I want to do:

    WHEN (5 seconds are gone || getCurrentDynamicExpr() == true) {
        processStuff();
    } 
    

    What's the right and easy way to do this?

    EDIT

    Seems a lot of confusion here... Got to be more concrete:

    I want to capture a camera shot when it's focused, so I want to check AVCaptureDevice's isAdjustingFocus property (I'm using AVCaptureStillImageOutput), and then capture a shot. 5 seconds are for.. well, if it didn't focus then something is wrong, so take the picture anyway.

    I'm sorry about a confusion, thought it's something really common..

  • dreamzor
    dreamzor about 11 years
    Thanks! But I want to do something easy and built-in. Without manual event-management, you know, too lazy... :)
  • dreamzor
    dreamzor about 11 years
    Thanks, what do you mean by 'slow and difficult'? I need to find a moment when some condition becomes true (it's something about the device state, so I can only catch notifications or manually check it in some fixed moment of time), so you mean the infinite while() loop to check it?
  • dreamzor
    dreamzor about 11 years
    Seems great, how would you have done this without the timeout part? I mean, function start -> wait for a KVO -> function process / end
  • Ken Thomases
    Ken Thomases about 11 years
    Change your design. What you're asking doesn't make much sense. Don't wait or block. If your current function is truly blocking, then nothing on that thread can change the observed property, anyway, so you will never get a KVO notification (on that thread). What is it that's changing the property, anyway? (For example, a network download completing.) Knowing that might help us provide more appropriate strategies.
  • Ken Thomases
    Ken Thomases about 11 years
    I see from your update that you're using AVCaptureStillImageOutput. The [AV Foundation Programming Guide][developer.apple.com/library/ios/#documentation/AudioV‌​ideo/… says you can't rely on the KVO notification coming on any particular thread. So, you're going to have to redirect it to an appropriate thread anyway. All the more reason not to structure your code so that it attempts to block waiting for the property to change. Just return control to the event loop and process the notification when it arrives.
  • dreamzor
    dreamzor about 11 years
    Got your point! What does the last sentence mean? :) But why would a some dispatch queue created by me care about AVFoundation threads?..
  • healadin
    healadin about 11 years
    Ah. If you're waiting on an external condition (as opposed to waiting on a long processing task), then in whatever observes that condition change, e.g. the notification handler, signal the semaphore.
  • Ken Thomases
    Ken Thomases about 11 years
    I'm not sure what dispatch queue you're referring to or what you mean by saying a queue might or might not "care" about an AV Foundation thread. The general point is that in modern apps, you should almost never write code that blocks and waits for some asynchronous event to occur. You just set up some sort of event handler or completion handler or the like that the system will call in response to the event occurring. After you've set that up, your code should return control back to the framework. It shouldn't attempt to block.
  • dreamzor
    dreamzor about 11 years
    Yeah, I thought about all advices, and then just realised that while() loop is the easiest and thing for such a task.. Thanks to everyone for help!
  • Tommy
    Tommy about 11 years
    See the code above, there's really nothing to it. You share one instance variable between the outer observer and the inner block — a condition lock rather than a BOOL — and there's no busy wait.
  • dreamzor
    dreamzor about 11 years
    Hey, that's a nice thing! Didn't know about this. I will keep it in mind for the future, thanks!