How is BGContinuedProcessingTask intended to be used?

Hello,

I'm trying to adopt the new BGContinuedProcessingTask API, but I'm having a little trouble imagining how the API authors intended it be used. I saw the WWDC talk, but it lacked higher-level details about how to integrate this API, and I can't find a sample project.

I notice that we can list wildcard background task identifiers in our Info.plist files now, and it appears this is to be used with continued tasks - a user might start one video encoding, then while it is ongoing, enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.

When it comes to implementing this, is the expectation that we:

a) Register a single handler for the wildcard pattern, which then figures out how to fulfil each request from the identifier of the passed-in task instance?

Or

b) Register a unique handler for each instance of the wildcard pattern? Since you can't unregister handlers, any resources captured by the handler would be leaked, so you'd need to make sure you only register immediately before submission - in other words register + submit should always be called as a pair.

Of course, I'd like to design my application to use this API as the authors intended it be used, but I'm just not entirely sure what that is. When I try to register a single handler for a wildcard pattern, the system rejects it at runtime (while allowing registrations for each instance of the pattern, indicating that at least my Info.plist is configured correctly). That points towards option B.

If it is option B, it's potentially worth calling that out in documentation - or even better, perhaps introduce a new call just for BGContinuedProcessingTask instead of the separate register + submit calls?

Thanks for your insight.

K


Aside: Also, it would be really nice if the handler closure would be async. Currently if you need to await on something, you need to launch an unstructured Task, but that causes issues since BGContinuedProcessingTask is not Sendable, so you can't pass it in to that Task to do things like update the title or mark the BGTask as complete.

Answered by DTS Engineer in 854482022

I'm trying to adopt the new BGContinuedProcessingTask API, but I'm having a little trouble imagining how the API authors intended it to be used. I saw the WWDC talk, but it lacked higher-level details about how to integrate this API, and I can't find a sample project.

So, as full disclosure, I helped prepare talk, and part of what we really struggled with here was the balance between presenting a simple, clear overview of the API and the FULL details of what was or was not possible. In general, the presentation tended toward simplicity, but that does mean that it's possible to overlook or misunderstand what's actually possible.

Let me start with what's probably the biggest misunderstanding, which leads to this issue:

Since you can't unregister handlers, any resources captured by the handler would be leaked.

This isn't really an issue as there's no reason your handler has to do any real "work" and, in practice, most apps probably SHOULDN'T actually do their real work there. Just like all of our other BackgroundTask types, all the handler does is tell your app that it can "start working". Returning from that handler does NOT mean that your task is fact "done", which is why you need to explicitly tell us using setTaskCompleted. I'm aware that the code snippet in the "Run the continuous background task" shows that work occurring inside the block the snippet is written that way because it was the simplest way to show the code flow, not because the work actually has to occur in that block.

I notice that we can list wildcard background task identifiers in our Info.plist files now, and it appears this is to be used with continued tasks - a user might start one video encoding, then while it is ongoing, enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.

First off, keep in mind that there's another option here which is to just have one "Encoding Video" task, which you then "add" additional work into using addChild(_:withPendingUnitCount:). This can be particularly important for operations which involve a mix of work types with different scheduling needs. For example, if an app is going to do something like:

  1. Download a (small) resource.
  2. Encode video based on information from #1.
  3. Upload (small) final result.

...then you'd generally want #1 & #3 to happen for "all" tasks immediately/simultaneously (to make sure you’re using the network efficiently), while #2 uses its own queueing to limit overall load. Putting that in concrete terms, if 8 jobs are submitted at the same time, you'd want to process them like this:

  • All 8 jobs perform #1.
  • Encoding starts on the first "X videos".
  • As encoding completes on one job, the next one immediately starts.
  • As each encoding completes, #3 starts independently of the encoding process.

You can't implement that approach if you use a separate BGContinuedProcessingTask for each job, but you can if you use a single BGContinuedProcessingTask with child progress.

Shifting to here:

...enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.

Correct, however, what I would actually emphasize here is that, particularly for long-running foreground apps, this is fundamentally an interface issue, not a work scheduling issue*. Putting that in concrete terms, an app doing extended encoding operations on a small number of large videos would probably want to create separate tasks for each operation, while an app doing a large number of much shorter operations would be better off using a single task.

*I'm confirming this with the engineering team, but I think that for large CPU/GPU-bound operations, it's likely that the system will allow your app to start more tasks than you'd actually want to execute simultaneously. In other words, the fact that the system will let you start 10 encode operations doesn't mean that's what you should actually "do".

However, in both cases, the choice largely depends on what looks/feels "right" to the overall user experience, not any particular technical requirement.

When I try to register a single handler for a wildcard pattern, the system rejects it at runtime (while allowing registrations for each instance of the pattern, indicating that at least my Info.plist is configured correctly). That points towards option B.

Yes, "B" is the primary usage pattern, though I can also imagine situations where a more "static" pattern is used.

If it is option B, it's potentially worth calling that out in documentation - or even better, perhaps introduce a new call just for BGContinuedProcessingTask instead of the separate register + submit calls?

I agree. If you haven't already, please file a bug on this and then post the bug number back here.

Aside: Also, it would be really nice if the handler closure would be async. Currently, if you need to await on something, you need to launch an unstructured Task, but that causes issues since BGContinuedProcessingTask is not Sendable, so you can't pass it into that Task to do things like update the title or mark the BGTask as complete.

I don't think this really works for the "general" register case. The BGAppRefreshTask block will often be called multiple times during a given process run, while BGProcessingTask will often not be run at all, neither of which really “fits" the async deferred work model. However, it's certainly worth considering if we add an API specific to BGContinuedProcessingTask.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I'm trying to adopt the new BGContinuedProcessingTask API, but I'm having a little trouble imagining how the API authors intended it to be used. I saw the WWDC talk, but it lacked higher-level details about how to integrate this API, and I can't find a sample project.

So, as full disclosure, I helped prepare talk, and part of what we really struggled with here was the balance between presenting a simple, clear overview of the API and the FULL details of what was or was not possible. In general, the presentation tended toward simplicity, but that does mean that it's possible to overlook or misunderstand what's actually possible.

Let me start with what's probably the biggest misunderstanding, which leads to this issue:

Since you can't unregister handlers, any resources captured by the handler would be leaked.

This isn't really an issue as there's no reason your handler has to do any real "work" and, in practice, most apps probably SHOULDN'T actually do their real work there. Just like all of our other BackgroundTask types, all the handler does is tell your app that it can "start working". Returning from that handler does NOT mean that your task is fact "done", which is why you need to explicitly tell us using setTaskCompleted. I'm aware that the code snippet in the "Run the continuous background task" shows that work occurring inside the block the snippet is written that way because it was the simplest way to show the code flow, not because the work actually has to occur in that block.

I notice that we can list wildcard background task identifiers in our Info.plist files now, and it appears this is to be used with continued tasks - a user might start one video encoding, then while it is ongoing, enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.

First off, keep in mind that there's another option here which is to just have one "Encoding Video" task, which you then "add" additional work into using addChild(_:withPendingUnitCount:). This can be particularly important for operations which involve a mix of work types with different scheduling needs. For example, if an app is going to do something like:

  1. Download a (small) resource.
  2. Encode video based on information from #1.
  3. Upload (small) final result.

...then you'd generally want #1 & #3 to happen for "all" tasks immediately/simultaneously (to make sure you’re using the network efficiently), while #2 uses its own queueing to limit overall load. Putting that in concrete terms, if 8 jobs are submitted at the same time, you'd want to process them like this:

  • All 8 jobs perform #1.
  • Encoding starts on the first "X videos".
  • As encoding completes on one job, the next one immediately starts.
  • As each encoding completes, #3 starts independently of the encoding process.

You can't implement that approach if you use a separate BGContinuedProcessingTask for each job, but you can if you use a single BGContinuedProcessingTask with child progress.

Shifting to here:

...enqueue another one from the same app, and these tasks would have identifiers such as "MyApp.VideoEncoding.ABCD" and "MyApp.VideoEncoding.EFGH" to distinguish them.

Correct, however, what I would actually emphasize here is that, particularly for long-running foreground apps, this is fundamentally an interface issue, not a work scheduling issue*. Putting that in concrete terms, an app doing extended encoding operations on a small number of large videos would probably want to create separate tasks for each operation, while an app doing a large number of much shorter operations would be better off using a single task.

*I'm confirming this with the engineering team, but I think that for large CPU/GPU-bound operations, it's likely that the system will allow your app to start more tasks than you'd actually want to execute simultaneously. In other words, the fact that the system will let you start 10 encode operations doesn't mean that's what you should actually "do".

However, in both cases, the choice largely depends on what looks/feels "right" to the overall user experience, not any particular technical requirement.

When I try to register a single handler for a wildcard pattern, the system rejects it at runtime (while allowing registrations for each instance of the pattern, indicating that at least my Info.plist is configured correctly). That points towards option B.

Yes, "B" is the primary usage pattern, though I can also imagine situations where a more "static" pattern is used.

If it is option B, it's potentially worth calling that out in documentation - or even better, perhaps introduce a new call just for BGContinuedProcessingTask instead of the separate register + submit calls?

I agree. If you haven't already, please file a bug on this and then post the bug number back here.

Aside: Also, it would be really nice if the handler closure would be async. Currently, if you need to await on something, you need to launch an unstructured Task, but that causes issues since BGContinuedProcessingTask is not Sendable, so you can't pass it into that Task to do things like update the title or mark the BGTask as complete.

I don't think this really works for the "general" register case. The BGAppRefreshTask block will often be called multiple times during a given process run, while BGProcessingTask will often not be run at all, neither of which really “fits" the async deferred work model. However, it's certainly worth considering if we add an API specific to BGContinuedProcessingTask.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

How is BGContinuedProcessingTask intended to be used?
 
 
Q