2 Replies
      Latest reply on Jan 13, 2020 1:32 PM by djs-code2
      djs-code2 Level 1 Level 1 (0 points)

        I have static library code from which I'm attempting to use NSProcessInfo.performExpiringActivity() to ensure that our app isn't background-terminated in the middle of a relatively short (yet important) periodic background task.  However, what's currently tripping me up is how NSProcessInfo.performExpiringActivity()'s block can be invoked more than once:

         

        let queue: DispatchQueue = DispatchQueue(label: "com.my-company.ios.foo", qos: .utility)
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.setEventHandler { [weak self] in
            guard let self = self, let managedObjectContext = self.managedObjectContext else {
                timer.cancel()
                return
            }
        
            timer.suspend()
            ProcessInfo.processInfo.performExpiringActivity(withReason: "Performing activity") { expired in
                defer {
                    timer.resume()
                }
        
                guard !expired else {
                    return
                }
        
                managedObjectContext.performAndWait {
                    ...
                }
            }
        }

         

        In this case, if the block is called a second time, the timer will be over-resumed and result in a crash.  I could add more book-keeping variables to guard against this, but it'll certainly make the code more difficult to maintain and reason about.

         

        Is there a more elegant and/or correct way to do this?  (Links to sample code or write-ups would be appreciated!)

        • Re: Using NSProcessInfo.performExpiringActivity() from a Dispatch Timer
          eskimo Apple Staff Apple Staff (12,975 points)

          Is there a more elegant and/or correct way to do this?

          Not that I’m aware of.  I’ve found performExpiringActivity(withReason:using:) to be very difficult to use in practice, and so I generally prefer to use the older UIApplication background task API (although that API is not with its pitfalls).  However, if you’re working on library code than you may not be able to rely on UIApplication (for example, if your\ library is loaded in an app extension).

          I could add more book-keeping variables to guard against this

          Even doing that is surprisingly hard.  The issue is that the two block invocations — to first to run the activity and the second if it expires — are run on different queues, so this bookkeeping has to be thread safe.

          How short is your operation?  If it’s short in human terms (0.1 second or less), your best option is to simply ignore expiry.  You seem to be doing that right now, but getting tripped up by your placement of the resume call.  I have concerns about its placement anyway.  The code snippet you posted has the resume nested within the timer’s event handler, which means that the time will only be resumed if it fires.  But it can’t fire because it was never resumed.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: Using NSProcessInfo.performExpiringActivity() from a Dispatch Timer
              djs-code2 Level 1 Level 1 (0 points)

              "The code snippet you posted has the resume nested within the timer’s event handler, which means that the time will only be resumed if it fires.  But it can’t fire because it was never resumed."

              Ah, that was a typo actually.  I've corrected it now to include the timer.suspend() before the activity block (which still has the problem I've described above).

               

              What I really want here is for the timer's logic, including the activity block, to not be re-entrant.  Overall, the timer should not fire again until the logic in the activity block has completed or expired.