Shorthand 检查同步锁定标志和退出函数?

Shorthand to check sync lock flag and exit function?

我试图阻止我的一个函数多次 运行 即使从几个不同的线程调用,但它看起来很笨拙。

这是我正在做的事情:

func doSomethingOnceAtATime() {
    var shouldExit = false
    DispatchQueue(label: "abcxyz123").sync {
        guard !inProgress else { shouldExit = true; return }
        inProgress = true
    }
    guard !shouldExit else { return }

    // Do something
}

我必须这样做,因为 sync 块中的 return 只退出那个闭包,而不是它所在的函数。有没有更快捷或更优雅的方法来做到这一点?

第一个问题是以下代码根本不提供同步:

DispatchQueue(label: "abcxyz123").sync { ... }

这将在您每次调用它时实例化一个新队列。相反,你应该有一个 属性 这是一个单一的串行队列,然后在你所有的 sync 调用中使用这个单一队列:

private let synchronizationQueue = DispatchQueue(label: "abcxyz123")

func someMethodThatNeedsSynchronizedAccess() {
    synchronizationQueue.sync { ... }
}

关于你不喜欢在块之前创建局部变量的问题,我很同情这一点。至少,我倾向于将这种模式从 doSomethingOnceAtATime 中分离出来,并隔离同步逻辑:

class TaskState {
    private var inProgress = false
    private let queue = DispatchQueue(label: "...")

    func attemptSetInProgress() -> Bool {
        var succeed = false
        queue.sync {
            if !inProgress {
                succeed = true
                inProgress = true
            }
        }
        return succeed
    }

    func unsetInProgress() {
        queue.sync {
            inProgress = false
        }
    }
}

然后,您的 doSomethingOnceAtATime 变得更加直观:

let state = TaskState()

func doSomethingOnceAtATime() {
    if !state.attemptSetInProgress() { return }

    // Do something

    state.unsetInProgress()
}

但这仍然有局部变量(尽管不可否认,封装在更合乎逻辑的级别)。如果连这都困扰你,我们可以利用 sync 方法 rethrows 这一事实来消除它。所以我们可以这样做:

class TaskState {

    enum TaskStateError: Error {
        case alreadyInProgress
    }

    enum State {
        case inProgress
        case notInProgress
    }

    private var state = State.notInProgress
    private let syncQueue = DispatchQueue(label: "sync")

    /// Try changing task status, if we can.
    ///
    /// - Note: Throw error if state already "in progress" and trying to change it to "in progress" again.

    func change(to newState: State) throws {
        try syncQueue.sync {
            if state == .inProgress && newState == .inProgress {
                throw TaskStateError.alreadyInProgress
            } else {
                state = newState
            }
        }
    }

}

请注意,以上内容还概括了 "state"(以防您搬到过两个以上的州)。

那么你可以这样做:

let state = TaskState()

func doSomethingOnceAtATime() {
    do { try state.change(to: .inProgress) } catch { return }

    // Do something

    try? state.change(to: .notInProgress)
}

我必须承认,虽然这摆脱了局部变量,但我个人认为它并不比之前的模式更好。但这取决于你。