如何逐个运行多个后台线程任务?

3
我正在尝试循环遍历一个包含2016年10月份日期的字符串对象数组,也就是31个字符串对象:从10月1日到10月31日。对于每个对象,我想从数据库中检索一些数据,并将返回值(同样是字符串对象)附加到新数组中。然而,棘手的部分在于,新的字符串对象数组应该按照与日期数组完全相同的顺序排列。例如,新数组中的第5个对象应该是我日期数组中第5个对象(2016年10月5日)的返回值,新数组中的第14个对象应该是我日期数组中第14个对象(2016年10月14日)的返回值,以此类推。现在显然,数据库检索过程发生在后台线程中,并且假设系统希望整个过程尽快完成,它会同时启动多个检索任务(都在自己的线程中)。问题在于,这真的混乱了新数组的构建顺序。老实说,它看起来非常令人困惑,奇怪的是数组的顺序并不是随机的(这可能会证实我的假设):新数组的顺序基本上是正确顺序的前8个返回值,然后从第9个值开始,前8个值会重复出现,就像这样:
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

正如您在这个模式中所注意到的那样,它只获取8个不同的值,然后一遍又一遍地重复,直到数组填满。如果我在很短的时间内运行整个循环过程两次,通常顺序仍然是相同的,除了数组中的第一个值不再相同(因此基本上每个值都向前移动1天)。无论如何,为了简化问题:我认为逐个运行每个检索任务将修复我的问题。这是我目前正在运行的循环:

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

我的模型将返回的值追加到一个新数组中,并将新创建的数组传回给调用者,如下所示:
// Delegate method which gets called whenever retrieval is finished
func messagesRetrieved() {
    // Pass newly created array back to caller
    self.messagesForDatesToDisplay = self.retrieveModel.messages
}

上述代码是我在项目中运行的实际代码的简化版本,但你可以理解其中的意思。第一个问题是:我的假设是否正确导致了这个问题?第二个跟进问题是:如果我是正确的,如何确保第二个检索过程不会在第一个检索过程完全完成之前开始(即在调用和运行返回值的委托方法之后)?


为什么不能使用NSOperationQueue,在其中可以将每个操作串行添加。 - Santosh
1个回答

1
一种方法是将结果检索到一个不依赖于结果顺序的结构中,例如字典。
因此,您可以执行以下操作:
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
}

个人而言,我会坚持使用字典结构来存储结果。

例如,如果我想获取第三个条目(“10月3日”),那么代码应该是:

let oct3Messages = modelDictionary[datesToDisplay[2]]

但如果您真的感觉有必要将其转换回原始顺序的数组,您可以这样做:

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

现在,我进行了一些微小的更改。例如,我向getMessagesForDate添加了一个完成处理程序,以便知道请求何时完成,并通过该闭包将结果传回。您希望避免异步更新模型对象,并且要知道何时完成所有操作,因此我使用调度组来协调。我还使用同步队列来协调结果更新以确保线程安全。
但我不希望您迷失在这些细节中。关键是,您应该只想使用不依赖于对象检索顺序的结构。然后,直接从该无序字典中检索(使用有序的datesToDisplay作为键),或将其转换为有序数组。

为了充分披露,我们应该说这可能比我在这里建议的更加复杂。例如,如果您的异步getMessagesForDate只是使用全局队列,您可能希望做一些限制同时运行多少个此类操作。

而且,您可能还想将其与按顺序执行这些请求进行基准测试,因为虽然数据库可以在后台线程上运行,但它可能无法并发地运行多个请求(通常,数据库会同步其查询),因此您可能正在进行比必要更多的工作。


谢谢!将检索到的对象放入字典中对我起了作用。我考虑过这样做,但是对于使用DispatchGroup类并不太熟悉。最终我通过循环遍历字典,并将每个键的值分配给相应的索引,将其转换回有序数组,所以我多做了一些工作,但现在它完美运行了!再次感谢清晰的解释! - Freddy Benson
没问题。顺便说一下,你说“通过循环遍历字典并分配值将其转换回有序数组”...显然,你也可以使用map来做到这一点,就像上面展示的那样,但如果你有可行的方法,那就太好了! - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接