【问题标题】:How to run multiple background thread tasks one at a time?如何一次运行多个后台线程任务?
【发布时间】:2016-10-14 14:39:06
【问题描述】:

我正在尝试遍历包含 2016 年 10 月日期的字符串对象数组,这意味着 31 个字符串对象:2016 年 10 月 1 日...2016 年 10 月 31 日。对于每个对象,我想从数据库并将返回的值(也是一个字符串对象)附加到一个新数组中。不过,棘手的部分是 String 对象的新数组应该与日期数组的顺序完全相同。例如,新数组中的第 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 可以串行添加每个操作。

标签: arrays swift multithreading loops swift3


【解决方案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
}

就我个人而言,我会坚持使用字典结构来获取结果。

例如,如果我想获得第三个条目(“3 October”),那就是

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 类。我最终通过遍历字典并将每个键的值分配给它们适当的索引,最终将它变成了一个有序数组,所以我最终做了一些额外的工作,但这现在就像一个魅力!再次感谢您的明确解释!
  • 没问题。顺便说一句,您说“通过遍历字典并分配值将其转回有序数组”...显然,您也可以使用map 来做到这一点,如上所示,但是如果您有一些有用的东西,太好了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 2017-03-05
  • 2016-12-16
  • 1970-01-01
相关资源
最近更新 更多