简体   繁体   中英

Swift - Is checking whether a weak variable is nil or not thread-safe?

I have a process which runs for a long time and which I would like the ability to interrupt.

func longProcess (shouldAbort: @escaping ()->Bool) {

    // Runs a long loop and periodically checks shouldAbort(),
    // returning early if shouldAbort() returns true

}

Here's my class which uses it:

class Example {

    private var abortFlag: NSObject? = .init()

    private var dispatchQueue: DispatchQueue = .init(label: "Example")

    func startProcess () {
        let shouldAbort: ()->Bool = { [weak abortFlag] in
            return abortFlag == nil
        }

        dispatchQueue.async {
            longProcess(shouldAbort: shouldAbort)
        }
    }

    func abortProcess () {
        self.abortFlag = nil
    }
}

The shouldAbort closure captures a weak reference to abortFlag , and checks whether that reference points to nil or to an NSObject . Since the reference is weak , if the original NSObject is deallocated then the reference that is captured by the closure will suddenly be nil and the closure will start returning true . The closure will be called repeatedly during the longProcess function, which is occurring on the private dispatchQueue . The abortProcess method on the Example class will be externally called from some other queue. What if someone calls abortProcess() , thereby deallocating abortFlag , at the exact same time that longProcess is trying to perform the check to see if abortFlag has been deallocated yet? Is checking myWeakReference == nil a thread-safe operation?

You can create the dispatched task as a DispatchWorkItem , which has a thread-safe isCancelled property already. You can then dispatch that DispatchWorkItem to a queue and have it periodically check its isCancelled . You can then just cancel the dispatched as such point you want to stop it.


Alternatively, when trying to wrap some work in an object, we'd often use Operation , instead, which encapsulates the task in its own class quite nicely:

class SomeLongOperation: Operation {
    override func main() {
        // Runs a long loop and periodically checks `isCancelled`

        while !isCancelled {
            Thread.sleep(forTimeInterval: 0.1)
            print("tick")
        }
    }
}

And to create queue and add the operation to that queue:

let queue = OperationQueue()
let operation = SomeLongOperation()
queue.addOperation(operation)

And to cancel the operation:

operation.cancel()

Or

queue.cancelAllOperations()

Bottom line, whether you use Operation (which is, frankly, the “go-to” solution for wrapping some task in its own object) or roll-your-own with DispatchWorkItem , the idea is the same, namely that you don't need to have your own state property to detect cancellation of the task. Both dispatch queues and operation queues already have nice mechanisms to simplify this process for you.

I saw this bug ( Weak properties are not thread safe when reading SR-192 ) indicating that weak reference reads weren't thread safe, but it has been fixed, which suggests that (absent any bugs in the runtime), weak reference reads are intended to be thread safe.

Also interesting: Friday Q&A 2017-09-22: Swift 4 Weak References by Mike Ash

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM