【问题标题】:How to cancel an Asynchronous function in Swift如何在 Swift 中取消异步函数
【发布时间】:2020-07-03 22:51:24
【问题描述】:

在swift中,取消异步执行的常见做法是什么?

使用这个异步执行闭包的例子, 取消异步功能的方法是什么?

func getSumOf(array:[Int], handler: @escaping ((Int)->Void)) {
    //step 2
    var sum: Int = 0
    for value in array {
        sum += value
    }
    //step 3
    Globals.delay(0.3, closure: {
        handler(sum)
    })
}

func doSomething() {
    //setp 1
    self.getSumOf(array: [16,756,442,6,23]) { [weak self](sum) in
        print(sum)
        //step 4, finishing the execution
    }
}
//Here we are calling the closure with the delay of 0.3 seconds
//It will print the sumof all the passed numbers.

【问题讨论】:

    标签: swift


    【解决方案1】:

    很遗憾,这个问题没有通用的答案,因为它完全取决于您的异步实现。

    假设您的delay 是典型的幼稚实现:

    static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) {
            closure()
        }
    }
    

    这是不可取消的。

    但是您可以重新定义它以使用DispatchWorkItem。这是可以取消的:

    @discardableResult
    static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) -> DispatchWorkItem {
        let task = DispatchWorkItem {
            closure()
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval, execute: task)
        
        return task
    }
    

    通过使其返回@discardableResult,这意味着您可以像以前一样使用它,但如果您想取消它,请获取结果并将其传递。例如,您也可以定义异步 sum 例程以使用此模式:

    @discardableResult
    func sum(of array: [Int], handler: @escaping (Int) -> Void) -> DispatchWorkItem {
        let sum = array.reduce(0, +)
    
        return Globals.delay(3) {
            handler(sum)
        }
    }
    

    现在,doSomething 可以根据需要捕获返回的值并使用它来取消异步计划任务:

    func doSomething() {
        var task = sum(of: [16, 756, 442, 6, 23]) { sum in
            print(Date(), sum)
        }
        
        ...
    
        task.cancel()
    }
    

    您还可以使用Timer 实现delay

    @discardableResult
    static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) -> Timer {
        Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
            closure()
        }
    }
    

    @discardableResult
    func sum(of array: [Int], handler: @escaping (Int) -> Void) -> Timer {
        let sum = array.reduce(0, +)
    
        return Globals.delay(3) {
            handler(sum)
        }
    }
    

    但这一次,你要invalidate计时器:

    func doSomething() {
        weak var timer = sum(of: [16, 756, 442, 6, 23]) { sum in
            print(Date(), sum)
        }
    
        ...
        
        timer?.invalidate()
    }
    

    必须注意,上述场景是简单的“延迟”场景所独有的。这不是停止异步进程的通用解决方案。例如,如果异步任务包含一些耗时的for循环,则上述方法是不够的。

    例如,假设您正在 for 循环中进行一些非常复杂的计算(例如处理图像的像素、处理视频的帧等)。在这种情况下,由于没有抢先取消,您需要手动检查DispatchWorkItemOperation 是否已通过检查它们各自的isCancelled 属性来取消。

    例如,让我们考虑一个将所有小于 100 万的素数相加的运算:

    class SumPrimes: Operation {
        override func main() {
            var sum = 0
            
            for i in 1 ..< 1_000_000 {
                if isPrime(i) {
                    sum += i
                }
            }
            
            print(Date(), sum)
        }
        
        func isPrime(_ value: Int) -> Bool { ... }   // this is slow
    }
    

    (显然,这不是解决“素数之和小于 x”问题的有效方法,但这只是一个示例,用于说明目的。)

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

    我们将无法cancel 那个。一旦开始,就无法停止。

    但我们可以通过在循环中添加对isCancelled 的检查来取消它:

    class SumPrimes: Operation {
        override func main() {
            var sum = 0
            
            for i in 1 ..< 1_000_000 {
                if isCancelled { return }
                
                if isPrime(i) {
                    sum += i
                }
            }
            
            print(Date(), sum)
        }
        
        func isPrime(_ value: Int) -> Bool { ... }
    }
    

    let queue = OperationQueue()
    let operation = SumPrimes()
    queue.addOperation(operation)
    
    ...
    
    operation.cancel()
    

    归根结底,如果它不是简单的延迟,并且您希望它可以取消,则必须将其集成到可以异步运行的代码中。

    【讨论】:

      【解决方案2】:

      使用这个例子...,取消异步功能的方法是什么?

      使用该示例,没有这样的方法。避免打印总和的唯一方法是让self 在调用后立即在 0.3 秒内退出存在。

      (有一些方法可以制作可取消的计时器,但制作的计时器,假设它是delay,我认为是,不可可取消.)

      【讨论】:

        【解决方案3】:

        我不知道你的算法,但首先我有一些建议。

        • 如果您想延迟,请在 getSumOf 函数之外执行以适应单一职责。
        • 使用内置的reduce 函数以更好、更高效的方式对数组中的项目求和。

        您可以使用DispatchWorkItem 来构建可取消的任务。因此您可以删除getSumOf 函数并编辑doSomething 函数,如下所示。

        let yourArray = [16,756,442,6,23]
        
        let workItem = DispatchWorkItem {
            // Your async code goes in here
            let sum = yourArray.reduce(0, +)
            print(sum)
        }
        
        // Execute the work item after 0.3 second
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: workItem)
        
        // You can cancel the work item if you no longer need it
        workItem.cancel()
        

        您还可以查看OperationQueue 以进行高级使用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-09-23
          • 1970-01-01
          • 2018-06-26
          • 2021-08-28
          • 2020-11-24
          • 2022-01-19
          • 2020-03-09
          • 1970-01-01
          相关资源
          最近更新 更多