1

我有一个值类型,需要相对较长的计算才能生成它(> 1s)。我将此值类型包装在一个枚举中,该枚举表示它是当前正在计算还是可用:

enum Calculatable<T> {
    case calculated(T), calculating
}

问题是这个值是持久的。这意味着在长时间运行计算之后,当我获得更新的值时,我会尝试先将其持久化,然后再更改对业务逻辑可见的属性。如果持久化成功,则更新属性,如果失败,我想等待,然后再次尝试持久化;有个问题——如果计算的值依赖的任何其他值发生了变化,我们丢弃之前计算的值——停止尝试持久化它——并向Calculation队列提交一个新的操作——重新开始。我最初的尝试是这样的:

/// Wraps a value that takes a long time to be calculated.
public class CalculatedValueWrapper<T> {

    /// The last submitted `Calculation` to the queue.
    private weak var latestCalculation: Optional<Operation>

    /// The long running calculation that produces the up to date, wrapped value.
    private let longCalculation: (_ cancelIf: () -> Bool) -> T

    /// Calculation queue.
    private let queue: OperationQueue

    /// The calculated value.
    private var wrappedValue: Calculatable<T>

    /// Update the wrapped value.
    public func update() {

        // Don't set if already being calculated.
        if case .calculated(_)=self.wrappedValue { self.wrappedValue = .calculating }

        // Initiate new calculation.
        self.latestCalculation?.cancel()
        self.latestCalculation={
            let calc: Calculation = .init(self, self.longCalculation)
            self.queue.addOperation(calc)
            return calc
        }()
    }
}

/// Executes a long running calculation and persists the result once complete.
class Calculation<T>: Operation {

    /// The calculation.
    private let calculation: (_ cancelIf: () -> Bool) -> T

    /// The owner of the wrapped value we're calculating.
    private weak var owner: Optional<CalculatedValueWrapper<T>>
    
    func main() {
        let result=self.calculation(cancelIf: { [unowned self] in self.isCancelled })

        let persist: () -> Bool={

            // In case the persistence fails.
            var didPersist=false

            // Persist the result on the main thread.
            DispatchQueue.main.sync { [unowned self] in

                // The owner may have been deallocated between the time this dispatch item was submitted and the time it began executing.
                guard let owner=self.owner else { self.cancel(); return }

                // May have been cancelled.
                guard !self.isCancelled else { return }

                // Attempt to persist the calculated result.
                if let _=try? owner.persist(result)  { 
                    didPersist=true 
                }
            }

            // Done.
            return didPersist
        }

        // Persist the new result. If it fails, and we're not cancelled, keep trying until it succeeds.
        while !self.isCancelled && !self.persist() {
            usleep(500_000)
        }
    }
}

我会坚持这一点,但经过进一步研究,我注意到一种普遍的观点,即在DispatchQueueitems 和Operations. DispatchQueue例如,在这种情况下,似乎被认为更好的替代方法是DispatchQueue使用asyncAfter(deadline:execute:).

在我的情况下,该解决方案似乎更复杂,因为能够取消封装所有需要完成的所有操作的单个操作使得取消变得容易。我可以保留对最后Calculation执行的操作的引用,如果在主线程上提交另一个操作,而另一个操作正在进行时,我取消旧的,添加新的,这样我就知道旧的值不会持久化,因为它是在执行取消后在主线程上完成的;并且Calculation不得取消 a 以使其保持其结果。

在这种情况下,是否有一些事情DispatchQueues可以OperationQueues使替代解决方案更直接地实施?还是睡在这里完全没问题?

4

1 回答 1

0

做一个 RetryPersist NSOperation。在将其添加到队列之前,将其 isReady 设置为 false。让它执行 dispatchAfter 弱捕获自身以将其 isReady 设置为 true。现在您可以取消队列中的任何 RetryPersist 操作,如果它们未准备好、未取消的计数不为零,则您知道有一个等待执行等。

于 2021-03-17T07:46:56.973 回答