ios - Swift 3 GCD 锁定变量和 block_and_release 错误

标签 ios swift multithreading concurrency grand-central-dispatch

我正在使用 Swift 3 GCD 来在我的代码中执行一些操作。但我经常收到 _dispatch_call_block_and_release 错误。我想这个错误背后的原因是因为不同的线程修改相同的变量,但我不知道如何解决问题。这是我的代码和解释:

我有一个变量,可以在不同的线程中访问和修改:

var queueMsgSent: Dictionary<Date,BTCommand>? = nil


func lock(obj: AnyObject, blk:() -> ()) {
    objc_sync_enter(obj)
    blk()
    objc_sync_exit(obj)
}

func addMsgSentToQueue(msg: BTCommands) {

    if queueMsgSent == nil {
        queueMsgSent = Dictionary.init()
    }
    let currentDate = Date()
    lock(obj: queueMsgSent as AnyObject) {
        queueMsgSent?.updateValue(msg, forKey: currentDate)
    }
}

func deleteMsgSentWithId(id: Int) {

    if queueMsgSent == nil { return }

    for (date, msg) in queueMsgSent! {


        if msg.isAck() == false && msg.getId()! == id {
            lock(obj: queueMsgSent as AnyObject) {
                queueMsgSent?.removeValue(forKey: date)
            }
        }
   }
}

func runSent() -> Void {


    while(true) {
        if queueMsgSent == nil { continue }

        for (date, msg) in queueMsgSent! {

            if msg.isSent() == false {
                 mainSearchView?.btCom?.write(str: msg.getCommand()!)
                 msg.setSent(val: true)
                lastMsgSent = Date()
                continue
            }

            if msg.isAck() == true {
                lock(obj: queueMsgSent as AnyObject) {
                    queueMsgSent?.removeValue(forKey: date)
                }
                continue
            }



        }
   }

}

我启动 runSent 方法为:

  DispatchQueue.global().async(execute: runSent)

我需要runSent不断检查queueMsgSent内的一些条件,并且需要在主线程id中调用其他函数addMsgSentToQueueue和deleteMsgSentWithId。我正在使用一些锁定机制,但它无法正常工作

最佳答案

我强烈建议您使用Grand Central Dispatch提供的DispatchQueue(s),它们使多线程管理变得更加容易。

命令

让我们从您的命令类开始

class Command {
    let id: String
    var isAck = false
    var isSent = false

    init(id:String) {
        self.id = id
    }
}

队列

现在我们可以构建我们的Queue类,它将提供以下功能

This is our class should not be confused with the concept of DispatchQueue!

  1. 命令插入队列
  2. 从队列中删除命令
  3. 开始处理队列中的所有元素

现在是代码:

class Queue {
    typealias Element = (date:Date, command:Command)
    private var storage: [Element] = []
    private let serialQueue = DispatchQueue(label: "serialQueue")

    func push(command:Command) {
        serialQueue.async {
            let newElement = (Date(), command)
            self.storage.append(newElement)
        }
    }

    func delete(by id: String) {
        serialQueue.async {
            guard let index = self.storage.index(where: { $0.command.id == id }) else { return }
            self.storage.remove(at: index)
        }
    }

    func startProcessing() {
        Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
            self.processElements()
        }
    }

    private func processElements() {
        serialQueue.async {
            // send messages where isSent == false
            let shouldBeSent = self.storage.filter { !$0.command.isSent }
            for elm in shouldBeSent {
                // TODO: add here code to send message
                elm.command.isSent = true
            }

            // remove from storage message where isAck == true
            self.storage = self.storage.filter { !$0.command.isAck }
        }
    }
}

它是如何工作的?

正如您所看到的,storage 属性是一个包含元组列表的数组,每个元组都有 2 个组件:DateCommand

由于 storage 数组是由多个线程访问的,因此我们需要确保以线程安全的方式访问它。

因此,每次访问存储时,我们都会将代码包装到其中

serialQueue.async {
    // access self.storage safely
}

我们写入上面所示的闭包👆👆👆的每个代码都会添加到我们的串行调度队列中。

串行队列当时确实处理了 1 个闭包。这就是为什么我们的存储属性以线程安全的方式访问!

enter image description here

最终考虑

下面的代码块是邪恶的

while true {
    ...
}

它确实使用了所有可用的 CPU 时间,它确实卡住了 UI(在主线程上执行时)并放电了电池。

如你所见,我将其替换为

Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
    self.processElements()
}

每 10 秒调用一次 self.processElements(),为 CPU 留下足够的时间来处理其他线程。

当然,您可以更改秒数以更好地适应您的场景。

关于ios - Swift 3 GCD 锁定变量和 block_and_release 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45640868/

相关文章:

ios - CGAffineTransformRotate 在执行旋转之前移动对象

ios - 在 UIWebView 中从 AVPlayer 获取视频 URL

ios - 二元运算符 '&' 不能应用于类型 'SCNetworkReachabilityFlags' 和 'Int' 的操作数

swift - iOS 13 UINavigationBar 在 UINavigationItem 变化时不会自动计算大小

ios - 如果元素符合给定协议(protocol),则扩展数组以符合协议(protocol)

一旦他分配的任务之一因任何原因失败,Java 就会停止执行程序服务

ios - 为 iOS 存储日志的正确方法

ios - 我是否必须升级到 Lion 才能在运行 iOS 5.1 的 iPad 上进行测试?

c# - .NET Socketserver 最大并发连接数和积压的最大值

python - 无需等待的异步任务队列