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

时间:2016-10-14 14:39:06

标签: arrays swift multithreading loops swift3

我试图遍历包含2016年10月日期的String对象数组,这意味着31个String对象:2016年10月1日... 2016年10月31日。对于每个对象,我想要检索一些数据从数据库中将返回的值(也是String对象)附加到新数组。但是,棘手的部分是新的String对象数组应该与dates数组的顺序完全相同。因此,例如,新数组中的第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
}

上面的代码是我在项目中运行的实际代码的简化版本,但是你明白了。第一个问题是:我是否接近正确的方向,我假设可能导致这个问题?第二个后续问题是:如果我是正确的,我怎么能确定第二个检索过程没有开始,直到第一个检索过程完全完成(所以在委托方法返回值后调用并运行) )?

1 个答案:

答案 0 :(得分: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只是使用全局队列,您可能想要做一些事情来约束其中有多少并发运行。

并且您可能还希望对此顺序执行这些请求进行基准测试,因为虽然数据库可能在后台线程上运行,但它可能无法并发运行多个请求(通常,数据库将同步它的查询),所以你可能会经历超出必要的工作。