ios - 即使已取消并设置为Nil,Swift iOS -DispatchWorkItem仍在运行

标签 ios swift grand-central-dispatch dispatchworkitem

我使用GCD的DispatchWorkItem跟踪正在发送到Firebase的数据。

我要做的第一件事是声明DispatchWorkItem类型的2个类属性,然后当我准备将数据发送到firebase时,我使用值对其进行初始化。

第一个属性名为errorTask。初始化后,将其cancels设置为firebaseTask,然后将其设置为nil,然后显示“errorTask fired”。它具有一个DispatchAsync Timer,如果在此之前未取消errorTask,它将在0.0000000001秒内调用它。

第二个属性名为firebaseTask。初始化后,它包含一个将数据发送到Firebase的函数。如果firebase回调成功,则取消errorTask并将其设置为nil,然后打印一条打印语句“到达firebase回调”。我还检查一下firebaseTask是否被取消了。

问题是errorTask内部的代码始终在到达firebaseTask回调之前运行。 errorTask代码取消了firebaseTask并将其设置为nil,但是由于某些原因,firebaseTask仍然运行。我不知道为什么吗?

打印语句支持以下事实:errorTask首先运行,因为"errorTask fired"总是在"firebase callback was reached"之前打印。

为什么即使errorTask使这些事情发生,firebaseTask仍不会被取消并设置为nil呢?

在我的实际应用程序中,如果用户向Firebase发送一些数据,则会出现事件指示器。一旦达到Firebase回调,事件指示器将被关闭,并向用户显示一条警报,表明已成功。但是,如果事件指示器上没有计时器,并且永远不会到达回调,那么它将永远旋转。 DispatchAsyc after的计时器设置为15秒,如果未达到回调,则会显示错误标签。 10次​​中有9次始终有效。

  • 将数据发送到FB
  • 显示事件指示器
  • 到达
  • 回调,因此取消errorTask,将其设置为nil,并关闭事件指示器
  • 显示成功警报。

  • 但是每隔一段时间
  • 这将花费15秒以上的时间
  • firebaseTask被取消并设置为nil,事件指示器将被驳回
  • 错误标签将显示
  • 成功警报仍会出现
  • errorTask代码块关闭actiInd,显示errorLabel,然后取消firebaseTask并将其设置为nil。一旦firebaseTask被取消并设置为nil,我就认为其中的所有内容都会停止,因为从未实现过回调。 这可能是造成我困惑的原因。 看来,即使firebaseTask被取消并设置为nil,someRef?.updateChildValues(...仍在运行,我也需要取消它。

    我的代码:
    var errorTask:DispatchWorkItem?
    var firebaseTask:DispatchWorkItem?
    
    @IBAction func buttonPush(_ sender: UIButton) {
    
        // 1. initialize the errorTask to cancel the firebaseTask and set it to nil
        errorTask = DispatchWorkItem{ [weak self] in
            self?.firebaseTask?.cancel()
            self?.firebaseTask = nil
            print("errorTask fired")
            // present alert that there is a problem
        }
    
        // 2. if the errorTask isn't cancelled in 0.0000000001 seconds then run the code inside of it
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.0000000001, execute: self.errorTask!)
    
        // 3. initialize the firebaseTask with the function to send the data to firebase
        firebaseTask = DispatchWorkItem{ [weak self]  in
    
            // 4. Check to see the if firebaseTask was cancelled and if it wasn't then run the code
            if self?.firebaseTask?.isCancelled != true{
                self?.sendDataToFirebase()
            }
    
           // I also tried it WITHOUT using "if firebaseTask?.isCancelled... but the same thing happens
        }
    
        // 5. immediately perform the firebaseTask
        firebaseTask?.perform()
    }
    
    func sendDataToFirebase(){
    
        let someRef = Database.database().reference().child("someRef")
    
        someRef?.updateChildValues(myDict(), withCompletionBlock: {
            (error, ref) in
    
            // 6. if the callback to firebase is successful then cancel the errorTask and set it to nil
            self.errorTask?.cancel()
            self.errorTask? = nil
    
            print("firebase callback was reached")
        })
    
    }
    

    最佳答案

    这个取消例程并没有按照我怀疑的方式执行。取消DispatchWorkItem时,它不执行抢先取消。当然,它与updateChildValues调用无关。它所做的只是对isCancelled属性执行线程安全设置,如果您手动遍历循环,则可以看到该任务已取消,因此可以定期检查并过早退出。

    结果,在任务开始时检查isCancelled并不是非常有用的模式,因为如果尚未创建任务,则没有任何要取消的操作。或者,如果任务已经创建并添加到队列中,并且在队列有机会启动之前被取消,则显然它将被取消但从未启动,因此您将永远无法进行isCancelled测试。如果任务已经开始,则可能在调用isCancelled之前已经通过了cancel测试。

    最重要的是,尝试对cancel请求进行计时,以便在任务开始之后但尚未进行isCancelled测试之前准确地接收到它们,这将是徒劳的。您的比赛几乎不可能完美地计时。此外,即使您确实恰好安排了时间,这也仅说明了整个过程的效率低下(百万个cancel请求中只有1个会达到您的预期)。

    通常,如果您有要取消的异步任务,则将其包装在异步自定义Operation子类中,并实现一个停止基础任务的cancel方法。与分派(dispatch)队列相比,操作队列为取消异步任务提供了更多优美的模式。但是所有这些都假定底层的异步任务提供了一种取消它的机制,我不知道Firebase是否甚至提供了一种有意义的机制来取消它。我当然没有在他们的任何例子中看到它。因此,所有这些可能都没有意义。

    我建议您远离问题中的特定代码模式,并描述您要完成的工作。让我们不要再为解决更广泛的问题而专门尝试解决问题,而是让我们了解更广泛的目标是什么,然后我们可以讨论如何解决这个问题。

    顺便说一句,您的示例中还有其他技术问题。

    具体来说,我假设您正在主队列上运行它。因此,task.perform()立即在当前队列上运行它。但是,您的DispatchQueue.main.asyncAfter(...)仅在完成主队列上已完成的操作后才能运行。因此,即使您指定了0.0000000001秒的延迟,它实际上也不会在主队列可用之前运行(即,在您的perform在主队列上完成运行并且您已经通过isCancelled测试之后)。

    如果要测试运行任务和取消任务之间的竞争,则需要在其他线程上执行取消。例如,您可以尝试:

    weak var task: DispatchWorkItem?
    
    let item = DispatchWorkItem {
        if (task?.isCancelled ?? true) {
            print("canceled")
        } else {
            print("not canceled in time")
        }
    }
    
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.00001) {
        task?.cancel()
    }
    
    task = item
    DispatchQueue.main.async {
        item.perform()
    }
    

    现在,您可以玩各种延迟,并查看0.1秒延迟与0.0000000001秒之一之间的不同行为。并且您需要确保应用在测试之前就已经达到静止状态(例如,在按钮按下事件中进行操作,而不是在viewDidLoad中进行操作)。

    但这又只能说明整个练习是徒劳的。在任务开始到检查isCancelled属性之前,您将很难抓到该任务。如果您真的想以某种可重复的方式来显示取消逻辑,那么我们将不得不人为地实现这一点:
    weak var task: DispatchWorkItem?
    
    let queue = DispatchQueue(label: "com.domain.app.queue") // create a queue for our test, as we never want to block the main thread
    
    let semaphore = DispatchSemaphore(value: 0)
    
    let item = DispatchWorkItem {
        // You'd never do this in a real app, but let's introduce a delay
        // long enough to catch the `cancel` between the time the task started.
        //
        // You could sleep for some interval, or we can introduce a semphore
        // to have it not proceed until we send a signal.
    
        print("starting")
        semaphore.wait() // wait for a signal before proceeding
    
        // now let's test if it is cancelled or not
    
        if (task?.isCancelled ?? true) {
            print("canceled")
        } else {
            print("not canceled in time")
        }
    }
    
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
        task?.cancel()
        semaphore.signal()
    }
    
    task = item
    queue.async {
        item.perform()
    }
    

    现在,您将永远不会这样做,但这仅说明isCancelled确实可以工作。

    坦白说,您永远不会像这样使用isCancelled。如果进行一些较长的过程,通常可以使用isCancelled进程,在该过程中您可以定期检查isCancelled的状态,如果为true则退出。但这不是您的情况。

    所有这些的结论是,在任务开始时检查isCancelled不可能实现您期望的目标。

    关于ios - 即使已取消并设置为Nil,Swift iOS -DispatchWorkItem仍在运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48844169/

    相关文章:

    android - Android 设备上的程序化自动助理

    iphone - 调试器停止在 ASIHTTPRequest 的 scheduleReadStream 上没有错误

    swift - 在 Swift 4 中将数据转换为 DispatchData

    ios - 转义闭包捕获非转义参数 'completion' (Swift 5)

    ios - Swift 中的 Google SingIn 获取个人资料信息

    ios - 动画 GMSMapView

    ios - SPSession 会超时吗?

    ios - 表格 View 上下文菜单 : custom animation from row to a preview view controller

    swift - 我在 swift 2 中的 http get 请求中有一个错误

    ios - ScaleMode 根本不起作用