What are the best practices for multiple async requests in background-fetch?
Asked Answered
M

2

5

I have an app that can have more than one user account. I need to update all of them in background. The problem is:

  • time is limited (~30 sec but requests may take longer than that)
  • all requests are asynchronous

When should I call a completion handler?

Minos answered 5/4, 2015 at 5:34 Comment(0)
M
7

Grand Central Dispatch's groups were basically made to solve this problem. From Apple's documentation on the subject:

A dispatch group is a way to monitor a set of block objects for completion. (You can monitor the blocks synchronously or asynchronously depending on your needs.) Groups provide a useful synchronization mechanism for code that depends on the completion of other tasks. For more information about using groups, see Waiting on Groups of Queued Tasks.

There are two ways you can use groups to monitor groups of tasks. The first is to use an async callback, and the other is to block the current queue until all of the grouped tasks have completed. The setup is the same either way.

I'll go through a quick example to get you started (I'll answer in Swift but the same approach carries over 1-1 with Objective-C). First, define your group:

let group = dispatch_group_create()

Enter the group once per each async task you'd like to complete:

dispatch_group_enter(group)
dispatch_group_enter(group)

Run your async tasks, and when you want to mark each task as completed, call dispatch_group_leave:

firstAsyncTask {
    dispatch_group_leave(group)
}

secondAsyncTask {
    dispatch_group_leave(group)
}

As mentioned above, when all tasks in the group have completed, you can either wait on the current queue (which will block the thread) or specify a block to be called asynchronously.

Wait

dispatch_group_wait(group, 30 * NSEC_PER_SEC)

This will stop executation on the current thread until either all of the group tasks have completed, or after 30s have elapsed (whichever is sooner).

If you want to remove any time limit:

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

Async

This one is a bit simpler, if only because there's not really much to it. You specify a block to call your block on as your second argument. Once all of the group's tasks are completed, this block is called:

dispatch_group_notify(group, dispatch_get_main_queue()) {
    // Code goes here.
}
Manamanacle answered 14/4, 2016 at 17:31 Comment(0)
E
3

I recently faced a similar situation and posted a question here.

The trick is to start all requests (asynchronously) and let each one of them execute a callback function that checks whether it was the last request or if there are still requests pending.

It it was indeed the last request, then the callback should execute the final completion handler.

The source code can be copied into a playground directly from my answer to this question here:

Passing and storing closures/callbacks in Swift

Emalia answered 5/4, 2015 at 15:24 Comment(8)
Obviously there is an underlying problem if you are limited to 30s but your requests can take longer. What are you fetching and why is there a 30s limit ?Emalia
It’s a background fetch. They say that the time is limited to 30 sec, although the documentation only implies, saying that the faster the operation is done, the more likely you get called next time.Minos
There should be some other approach. Podcast apps for example can easily fetch several MBs of data in the background.Emalia
The data that I fetch is relatively small but the server may not respond, internet connection can be slow (edge for example). While I was testing my app, I noticed that sometimes it takes up to 5 seconds to finish a request.Minos
But five seconds is fine, if several requests can be executed parallel like in my example. As long as no single requests takes too long.Emalia
I don't know the exact setting but I think you'd be fine with that approachEmalia
It didn’t work, I receive Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called.Minos
Nope, playground doesn’t allow to imitate a background fetch.Minos

© 2022 - 2024 — McMap. All rights reserved.