Je veux ajouter des blocs donnés à un tableau, puis exécuter tous les blocs contenus dans le tableau, lorsque demandé. J'ai un code similaire à ceci:
class MyArrayBlockClass {
private var blocksArray: Array<() -> Void> = Array()
private let blocksQueue: NSOperationQueue()
func addBlockToArray(block: () -> Void) {
self.blocksArray.append(block)
}
func runBlocksInArray() {
for block in self.blocksArray {
let operation = NSBlockOperation(block: block)
self.blocksQueue.addOperation(operation)
}
self.blocksQueue.removeAll(keepCapacity: false)
}
}
Le problème vient du fait que addBlockToArray peut être appelé sur plusieurs threads. Ce qui se passe, c'est que addBlockToArray est appelé en succession rapide sur différents threads et qu'il ne fait qu'ajouter l'un des éléments. Par conséquent, l'autre élément n'est pas appelé lors de l'exécution de runBlocksInArray.
J'ai essayé quelque chose comme ça, qui ne semble pas fonctionner:
private let blocksDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
func addBlockToArray(block: () -> Void) {
dispatch_async(blocksDispatchQueue) {
self.blocksArray.append(block)
}
}
Vous avez défini votre blocksDispatchQueue
comme étant une file d'attente globale. En mettant à jour ceci pour Swift 3, l’équivalent est:
private let blocksDispatchQueue = DispatchQueue.global()
func addBlockToArray(block: @escaping () -> Void) {
blocksDispatchQueue.async {
self.blocksArray.append(block)
}
}
Le problème est que les files d'attente globales sont des files d'attente simultanées. Par conséquent, vous n'obtenez pas la synchronisation souhaitée. Mais si vous aviez créé votre propre file d’attente en série, cela aurait été bien, par exemple. dans Swift 3:
private let blocksDispatchQueue = DispatchQueue(label: "com.domain.app.blocks")
Cette file d'attente personnalisée est, par défaut, une file d'attente série. Ainsi, vous obtiendrez la synchronisation souhaitée.
Remarque: si vous utilisez cette blocksDispatchQueue
pour synchroniser votre interaction avec cette file d'attente, les interactions all avec cette blocksArray
doivent être coordonnées via cette file d'attente, par exemple. envoyez également le code pour ajouter les opérations à l'aide de la même file d'attente:
func runBlocksInArray() {
blocksDispatchQueue.async {
for block in self.blocksArray {
let operation = BlockOperation(block: block)
self.blocksQueue.addOperation(operation)
}
self.blocksArray.removeAll()
}
}
Vous pouvez également utiliser le modèle lecteur/graveur pour créer votre propre file d'attente simultanée:
private let blocksDispatchQueue = DispatchQueue(label: "com.domain.app.blocks", attributes: .concurrent)
Mais dans le modèle lecteur-écrivain, les écritures doivent être effectuées en utilisant une barrière (obtenant un comportement de type série pour les écritures):
func addBlockToArray(block: @escaping () -> Void) {
blocksDispatchQueue.async(flags: .barrier) {
self.blocksArray.append(block)
}
}
Mais vous pouvez maintenant lire les données, comme ci-dessus:
blocksDispatchQueue.sync {
someVariable = self.blocksArray[index]
}
L'avantage de ce modèle est que les écritures sont synchronisées, mais que les lectures peuvent avoir lieu simultanément l'une par rapport à l'autre. Ce n'est probablement pas critique dans ce cas (une simple file d'attente sérielle serait probablement suffisante), mais j'inclus ce motif de lecture/écriture pour des raisons de complétude.
Si vous recherchez des exemples de Swift 2, voyez le rendu précédent de cette réponse.
Pour la synchronisation entre les threads, utilisez dispatch_sync
(pas _async) et votre propre file d'attente de distribution (pas la file globale):
class MyArrayBlockClass {
private var queue = dispatch_queue_create("andrew.myblockarrayclass", nil)
func addBlockToArray(block: () -> Void) {
dispatch_sync(queue) {
self.blocksArray.append(block)
}
}
//....
}
dispatch_sync
est agréable et facile à utiliser et devrait suffire à votre cas (je l’utilise pour tous mes besoins de synchronisation de threads pour le moment), mais vous pouvez également utiliser des verrous et des mutex de niveau inférieur. Mike Ash a rédigé un excellent article sur différents choix: Serrures, sécurité du fil et Swift
Créez une file d'attente série et apportez des modifications au tableau dans ce thread. Votre appel de création de thread devrait ressembler à ceci:
private let blocksDispatchQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)
Ensuite, vous pouvez l'utiliser de la même manière que maintenant.
func addBlockToArray(block: () -> Void) {
dispatch_async(blocksDispatchQueue) {
self.blocksArray.append(block)
}
}
import Foundation
class AtomicArray<T> {
private lazy var semaphore = DispatchSemaphore(value: 1)
private var array: [T]
init (array: [T]) { self.array = array }
func append(newElement: T) {
wait(); defer { signal() }
array.append(newElement)
}
subscript(index: Int) -> T {
get {
wait(); defer { signal() }
return array[index]
}
set(newValue) {
wait(); defer { signal() }
array[index] = newValue
}
}
var count: Int {
wait(); defer { signal() }
return array.count
}
private func wait() { semaphore.wait() }
private func signal() { semaphore.signal() }
func set(closure: (_ curentArray: [T])->([T]) ) {
wait(); defer { signal() }
array = closure(array)
}
func get(closure: (_ curentArray: [T])->()) {
wait(); defer { signal() }
closure(array)
}
func get() -> [T] {
wait(); defer { signal() }
return array
}
}
extension AtomicArray: CustomStringConvertible {
var description: String { return "\(get())"}
}
L'idée de base est d'utiliser la syntaxe d'un tableau régulier
let atomicArray = AtomicArray(array: [3,2,1])
print(atomicArray)
atomicArray.append(newElement: 1)
let arr = atomicArray.get()
print(arr)
atomicArray[2] = 0
atomicArray.get { currentArray in
print(currentArray)
}
atomicArray.set { currentArray -> [Int] in
return currentArray.map{ item -> Int in
return item*item
}
}
print(atomicArray)
import UIKit
class ViewController: UIViewController {
var atomicArray = AtomicArray(array: [Int](repeating: 0, count: 100))
let dispatchGroup = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
arrayInfo()
sample { index, dispatch in
self.atomicArray[index] += 1
}
dispatchGroup.notify(queue: .main) {
self.arrayInfo()
self.atomicArray.set { currentArray -> ([Int]) in
return currentArray.map{ (item) -> Int in
return item + 100
}
}
self.arrayInfo()
}
}
private func arrayInfo() {
print("Count: \(self.atomicArray.count)\nData: \(self.atomicArray)")
}
func sample(closure: @escaping (Int,DispatchQueue)->()) {
print("----------------------------------------------\n")
async(dispatch: .main, closure: closure)
async(dispatch: .global(qos: .userInitiated), closure: closure)
async(dispatch: .global(qos: .utility), closure: closure)
async(dispatch: .global(qos: .default), closure: closure)
async(dispatch: .global(qos: .userInteractive), closure: closure)
}
private func async(dispatch: DispatchQueue, closure: @escaping (Int,DispatchQueue)->()) {
for index in 0..<atomicArray.count {
dispatchGroup.enter()
dispatch.async {
closure(index,dispatch)
self.dispatchGroup.leave()
}
}
}
}
NSOperationQueue
lui-même est thread-safe, vous pouvez donc définir suspended
sur true, ajouter tous les blocs souhaités à partir de n'importe quel thread, puis définir suspended
sur false pour exécuter tous les blocs.