Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 531
Jarod Avatar asked Apr 05 '15 05:04

Jarod


2 Answers

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.
}
like image 188
dwlz Avatar answered Nov 04 '22 14:11

dwlz


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

like image 3
Jan Avatar answered Nov 04 '22 14:11

Jan