How to Handle Asynchronous Operations in BGContinuedProcessingTask

I would like to know whether BGContinuedProcessingTaskRequest supports executing asynchronous tasks internally, or if it can only execute synchronous tasks within BGContinuedProcessingTaskRequest?

Our project is very complex, and we now need to use BGContinuedProcessingTaskRequest to perform some long-running operations when the app enters the background (such as video encoding/decoding & export). However, our export interface is an asynchronous function, for example video.export(callback: FinishCallback). This export call returns immediately, and when the export completes internally, it calls back through the passed-in callback. So when I call BGTaskScheduler.shared.register to register a BGContinuedProcessingTask, what should be the correct approach? Should I directly call video.export(nil) without any waiting, or should I wait for the export function to complete in the callback? For example:

    BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.xxx.xxx.xxx.xxx", using: nil) { task in
                guard let continuedTask = task as? BGContinuedProcessingTask else {
                    task.setTaskCompleted(success: false)
                    return
                }
                
                let scanner = SmartAssetsManager.shared
                let semaphore = DispatchSemaphore(value: 0)

                continuedTask.expirationHandler = {
                    logError(items: "xwxdebug finished.")
                    semaphore.signal()
                }

                logInfo(items: "xwxdebug start!")

                video.export { _ in
                    semaphore.signal()
                }
                
                
                semaphore.wait()
                logError(items: "xwxdebug finished!")
            }

I would like to know whether BGContinuedProcessingTaskRequest supports executing asynchronous tasks internally.

Yes, this is 100% supported. FYI, it uses the same approach as the other task types, so everything I'm describing equally applies to the other task types in the background task framework.

or if it can only execute synchronous tasks within BGContinuedProcessingTaskRequest?

No, not at all. You CAN choose to do your work inside the task block, but you are under no obligation to do so and, in fact, properly implemented solutions for work like you're describing:

...(such as video encoding/decoding & export). However, our export interface is an asynchronous function, for example, video.export(callback: FinishCallback).

...probably SHOULDN'T try and "force" their work into the task block model. Doing so would mean either compromising performance by constraining yourself to a single thread or something like a semaphore to artificially block the task block, both of which are not ideal. And, yes, the code snippet you include is exactly what I would NOT do.

Swift async can potentially avoid/mitigate these issues; however, the point of this API is to perform "extended work“ - that means "minutes", not "seconds". I have a difficult time thinking of work that runs for minutes at a time and is best implemented inside a single block callback.

So when I call BGTaskScheduler.shared.register to register a BGContinuedProcessingTask, what should be the correct approach?

In terms of the API, all that's required is that you provide regular progress and that you complete the task when you finish the work or the task expires. Whether or not you block or return in the launch handler block doesn't matter to the API.

Should I directly call video.export(nil) without any waiting, or should I wait for the export function to complete in the callback?

So, there's actually an inherent contradiction to the API that makes the answer to this somewhat complicated. I have a forum post here that goes into this in more detail, but quoting myself from the end of that post:

"...if an app has an existing complex data processing pipeline that already works but "should" now continue into the background, then the right answer probably ISN'T "rework my entire data flow so it fits into BGContinuedProcessing". That's a great way to waste a lot of time introducing new bugs into working code. The simpler answer will often be to "attach" the BGContinuedProcessingTask to that existing pipeline without actually changing your existing code in any large-scale way. If the BGContinuedProcessing fails to start when you "expect” it, then you continue with the work anyway, which is exactly what you were doing before iOS 26."

Putting that in concrete terms, if an app has a button labeled "Export Critical Document Immediately..." and the user presses that button, the LAST thing your app should do is defer/delay that work because BGContinuedProcessingTask hasn't fired. The user just told your app to do something, so your app’s job is to "do it".

On the other hand, what you actually described here:

...to perform some long-running operations when the app enters the background (such as video encoding/decoding & export).

Isn't necessarily an appropriate use of the API. The point of this API is to finish the work the user wanted your app to do "now", NOT perform "background work" your app was otherwise deferring. We have other APIs for handling low-priority work without disrupting the user. This API is for finishing high-priority work, which means work you can't defer or cancel.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

How to Handle Asynchronous Operations in BGContinuedProcessingTask
 
 
Q