How to run multiple tasks with background thread one at a time? (Swift 3)

First of all, I'm a beginner, and I'm going to make a few assumptions about what causes my problem here, which may seem really stupid, so please tell me.

I am trying to skip an array of String objects containing the dates of the month of October 2016, which means 31 String objects: October 1, 2016 ... October 31, 2016. For each object I want to get some data from the database and add a return value (also a String object ) into a new array. However, the tricky part is that the new array of String objects must be in the same order as the date array. So, for example, the 5th object in the new array should be the returned value of the 5th object in my date array (October 5, 2016), and the 14th object in the new array should be the returned value of the 14th object in my array dates (October 14, 2016), etc. Etc. Now it’s obvious that the database search process takes place in the background thread, and assuming that the system wants the entire process to be executed as quickly as possible, it simultaneously launches several search tasks (all in its stream). The problem is that it really messed up the order in which the new array is built. Honestly, this looks very confusing, and the strange part is that the order of the array is not random (which would probably confirm my assumption): the order of the new array is basically the first 8 returned values ​​in the correct order and then the first 8 from the 9th value values ​​are repeated, so it seems to be:

1 October -> 5 2 October -> 8 3 October -> 4 4 October -> 11 5 October -> 9 6 October -> 7 7 October -> 6 8 October -> 14 9 October -> 5 10 October -> 8 11 October -> 4 12 October -> 11 13 October -> 9 14 October -> 7 15 October -> 6 16 October -> 14 17 October -> 5 18 October -> 8 19 October -> 4 20 October -> 11 21 October -> 9 22 October -> 7 23 October -> 6 24 October -> 14 25 October -> 5 26 October -> 8 27 October -> 4 28 October -> 11 29 October -> 9 30 October -> 7 31 October -> 6 

So, as you can see in this pattern, it just extracts 8 different values, and then repeats again until the array is full. If I started the entire cycle of the cycle in a very short time, the order usually remains the same, except that the first value in the array is no longer the same (so basically every value moves to 1 date). In any case, to move on to the pursuit: I assume that completing each search task one at a time will fix my problem. This is the loop that I am currently executing:

 // Loop through each date for date in self.datesToDisplay { // Fire off retrieval method with date object as its only parameter self.getMessagesForDate(date) } 

My model will add the return values ​​to the new array and pass the newly created array back to the caller, for example:

 // Delegate method which gets called whenever retrieval is finished func messagesRetrieved() { // Pass newly created array back to caller self.messagesForDatesToDisplay = self.retrieveModel.messages } 

The above code is a slightly simplified version of the actual code that I run in my project, but you get the idea. First question: am I somewhere close to the right direction with my assumption of what might cause this problem? The second question is about the next steps: if I am right, how can I be sure that the second search process does not start until the first is COMPLETED (so after the delegate method to return the value was called and started)?

+3
source share
1 answer

One approach is to get the results in a structure that is independent of the order in which the results come, for example, in a dictionary.

So you can do something like:

 let syncQueue = DispatchQueue(label: "...") // use dispatch_queue_create() in Swift 2 let group = DispatchGroup() // use dispatch_group_create() in Swift 2 var results = [String: [Message]]() for date in datesToDisplay { group.enter() // use dispatch_group_enter in Swift 2 getMessages(for: date) { messages in syncQueue.async { // use dispatch_async in Swift 2 results[date] = messages group.leave() // use dispatch_group_leave in Swift 2 } } } group.notify(queue: .main) { // use dispatch_group_notify in Swift 2 syncQueue.sync { // use dispatch_sync in Swift 2 // update your model with `results` here } // trigger UI update here } 

Personally, I just stuck to the dictionary structure for the results.

For example, if I wanted to get the third record ("October 3"), it would be

 let oct3Messages = modelDictionary[datesToDisplay[2]] 

But if you are really forced to convert it to an array of the original order, you can do this:

 group.notify(queue: .main) { syncQueue.sync { self.retrieveModel = self.datesToDisplay.map { results[$0]! } } // trigger UI update here } 

Now I have made some minor changes. For example, I added a completion handler for getMessagesForDate , so I know when the request was executed, and I pass the results in this close. You want to update model objects asynchronously, and you want to know when everything is done, and I use the send group to coordinate it. I also use the synchronization queue to coordinate update updates to ensure thread safety.

But I do not want you to be mistaken in these details. The key point is that you should just want to use a structure that is independent of the order in which objects are restored. Then either extract directly from this unordered dictionary (using your ordered datesToDisplay as the key), or convert it to an ordered array.


In the interest of full disclosure, we must say that it can be more difficult than I suggest here. For example, if your asynchronous getMessagesForDate just uses a global queue, you can do something to limit how many of them are running at a time.

And you can also compare this with executing these queries sequentially, because although the database can run in the background thread, it may not be able to run several queries simultaneously with each other (often the database synchronizes its queries), so you can go through more work than necessary.

+1
source

All Articles