简体   繁体   English

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

[英]Swift iOS -DispatchWorkItem is still running even though it's getting Cancelled and Set to Nil

I use GCD's DispatchWorkItem to keep track of my data that's being sent to firebase. 我使用GCD的DispatchWorkItem来跟踪我发送到firebase的数据。

The first thing I do is declare 2 class properties of type DispatchWorkItem and then when I'm ready to send the data to firebase I initialize them with values. 我做的第一件事就是声明类型为DispatchWorkItem 2个类属性,然后当我准备好将数据发送到firebase时,我用值初始化它们。

The first property is named errorTask . 第一个属性名为errorTask When initialized it cancels the firebaseTask and sets it to nil then prints "errorTask fired". 初始化时cancels firebaseTask并将其设置为nil然后打印“errorTask fired”。 It has a DispatchAsync Timer that will call it in 0.0000000001 seconds if the errorTask isn't cancelled before then. 它有一个DispatchAsync Timer ,如果在此之前没有取消errorTask,它将在0.0000000001秒内调用它。

The second property is named firebaseTask . 第二个属性名为firebaseTask When initialized it contains a function that sends the data to firebase. 初始化时,它包含一个将数据发送到firebase的函数。 If the firebase callback is successful then errorTask is cancelled and set to nil and then a print statement "firebase callback was reached" prints. 如果errorTask回调成功,则取消errorTask并将其设置为nil ,然后打印出打印语句“firebase callback”。 I also check to see if the firebaseTask was cancelled. 我还检查firebaseTask是否被取消。

The problem is the code inside the errorTask always runs before the firebaseTask callback is reached. 问题是里面的代码errorTask始终运行之前firebaseTask达到回调。 The errorTask code cancels the firebaseTask and sets it to nil but for some reason the firebaseTask still runs. errorTask代码取消firebaseTask并将它设置为零,但由于某种原因, firebaseTask仍然运行。 I can't figure out why? 我想不通为什么?

The print statements support the fact that the errorTask runs first because "errorTask fired" always gets printed before "firebase callback was reached" . print语句支持errorTask首先运行的事实,因为"errorTask fired"总是在"firebase callback was reached" "errorTask fired" "firebase callback was reached"之前打印。

How come the firebaseTask isn't getting cancelled and set to nil even though the errorTask makes those things happen? 为什么firebaseTask没有被取消并设置为nil,即使errorTask让这些事情发生了?

Inside my actual app what happens is if a user is sending some data to Firebase an activity indicator appears. 在我的实际应用程序内部,如果用户向Firebase发送一些数据,则会出现活动指示符。 Once the firebase callback is reached then the activity indicator is dismissed and an alert is shown to the user saying it was successful. 达到firebase回调后,活动指示器将被解除,并向用户显示一条提示已成功的警报。 However if the activity indicator doesn't have a timer on it and the callback is never reached then it will spin forever. 但是,如果活动指示器上没有计时器并且从未达到回调,那么它将永远旋转。 The DispatchAsyc after has a timer set for 15 secs and if the callback isn't reached an error label would show. DispatchAsyc after将计时器设置为15秒,如果未达到回调,则会显示错误标签。 9 out of 10 times it always works . 它总是有效的10次中的9次。

  1. send data to FB 将数据发送到FB
  2. show activity indicator 显示活动指标
  3. callback reached so cancel errorTask, set it to nil, and dismiss activity indicator 达到回调所以取消errorTask,将其设置为nil,并关闭活动指示器
  4. show success alert. 显示成功警报。

But every once in while 但每一次都在

  1. it would take longer then 15 secs 这需要15秒以上的时间
  2. firebaseTask is cancelled and set to nil, and the activity indicator would get dismissed firebaseTask被取消并设置为nil,活动指示器将被取消
  3. the error label would show 错误标签会显示
  4. the success alert would still appear 成功警报仍然会出现

The errorTask code block dismisses the actiInd, shows the errorLabel, and cancels the firebaseTask and sets it to nil. 所述errorTask代码块驳回actiInd,示出了errorLabel,并取消firebaseTask并将其设置为零。 Once the firebaseTask is cancelled and set to nil I assumed everything inside of it would stop also because the callback was never reached. 一旦firebaseTask被取消并设置为nil,我假设其中的所有内容也将停止,因为从未到达回调。 This may be the cause of my confusion. 这可能是我混淆的原因。 It seems as if even though the firebaseTask is cancelled and set to nil, someRef?.updateChildValues(... is somehow still running and I need to cancel that also. 看起来好像即使firebaseTask被取消,并设置成零, someRef?.updateChildValues(...不知何故仍然在运行,我需要取消之谓也。

My code: 我的代码:

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")
    })

}

This cancel routine is not doing what I suspect you think it is. 这个取消程序没有做我怀疑你认为的那样。 When you cancel a DispatchWorkItem , it performs no preemptive cancellation. 取消DispatchWorkItem ,它不执行抢先取消。 It certainly has no bearing on the updateChildValues call. 它肯定与updateChildValues调用没有关系。 All it does is perform a thread-safe setting of the isCancelled property, which if you were manually iterating through a loop, you could periodically check and exit prematurely if you see that the task was canceled. 它所做的只是执行isCancelled属性的线程安全设置,如果您手动迭代循环,如果您发现任务被取消,您可以定期检查并提前退出。

As a result, the checking of isCancelled at the start of the task isn't terribly useful pattern, because if the task has not yet been created, there is nothing to cancel. 因此,在任务开始时检查isCancelled并不是非常有用的模式,因为如果尚未创建任务,则没有任何内容可以取消。 Or if the task has been created and added to a queue, and canceled before the queue had a chance to start, it will obviously just be canceled but never started, you'll never get to your isCancelled test. 或者,如果任务已创建并添加到队列中,并在队列有机会启动之前取消,则显然只会取消但从未启动过,您将永远无法进入isCancelled测试。 And if the task has started, it's likely gotten past the isCancelled test before cancel was called. 如果任务已经开始,那么在调用cancel之前可能已经超过了isCancelled测试。

Bottom line, attempts to time the cancel request so that they are received precisely after the task has started but before it has gotten to the isCancelled test is going to be an exercise in futility. 最重要的是,尝试对cancel请求进行计时,以便在任务开始之后但在进入isCancelled测试之前准确接收cancel请求将是徒劳的。 You have a race that will be almost impossible to time perfectly. 你的比赛几乎不可能完美。 Besides, even if you did happen to time this perfectly, this merely demonstrates how ineffective this whole process is (only 1 in a million cancel requests will do what you intended). 此外,即使你确实碰巧完美地计时,这也只能说明整个过程是多么无效(只有百万分之一的cancel请求会按你的意图行事)。

Generally, if you had asynchronous task that you wanted to cancel, you'd wrap it in an asynchronous custom Operation subclass, and implement a cancel method that stops the underlying task. 通常,如果您要取消异步任务,则将其包装在异步自定义Operation子类中,并实现一个停止基础任务的cancel方法。 Operation queues simply offer more graceful patterns for canceling asynchronous tasks than dispatch queues do. 操作队列只是提供了比调度队列更优雅的模式来取消异步任务。 But all of this presumes that the underlying asynchronous task offers a mechanism for canceling it and I don't know if Firebase even offers a meaningful mechanism to do that. 但所有这些都假设底层异步任务提供了一种取消它的机制,我不知道Firebase是否提供了一种有意义的机制来实现这一点。 I certainly haven't seen it contemplated in any of their examples. 我当然没有在他们的任何例子中看到它。 So all of this may be moot. 所以这一切都可能没有实际意义。

I'd suggest you step away from the specific code pattern in your question and describe what you are trying to accomplish. 我建议你离开问题中的特定代码模式,并描述你想要完成的事情。 Let's not dwell on your particular attempted solution to your broader problem, but rather let's understand what the broader goal is, and then we can talk about how to tackle that. 让我们不再详述您对更广泛问题的特定尝试解决方案,而是让我们了解更广泛的目标是什么,然后我们可以讨论如何解决这个问题。


As an aside, there are other technical issues in your example. 另外,您的示例中还有其他技术问题。

Specifically, I'm assuming you're running this on the main queue. 具体来说,我假设你在主队列上运行它。 So task.perform() runs it on the current queue immediately. 所以task.perform()立即在当前队列上运行它。 But your DispatchQueue.main.asyncAfter(...) can only be run when whatever is running on the main queue is done. 但是只有当主队列上运行的任何内容运行时,才能运行DispatchQueue.main.asyncAfter(...) So, even though you specified a delay of 0.0000000001 seconds, it actually won't run until the main queue is available (namely, after your perform is done running on the main queue and you're well past the isCancelled test). 因此,即使您指定了0.0000000001秒的延迟,它实际上也不会运行,直到主队列可用(即,在主队列上perform完成并且您已经远远超过isCancelled测试之后)。

If you want to test this race between running the task and canceling the task, you need to perform the cancel on a different thread. 如果要在运行任务和取消任务之间测试此争用,则需要在其他线程上执行取消。 For example, you could try: 例如,您可以尝试:

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()
}

Now you can play with various delays and see the different behavior between a delay of 0.1 seconds and one of 0.0000000001 seconds. 现在,您可以播放各种延迟,并查看延迟0.1秒和0.0000000001秒之间的不同行为。 And you'll want to make sure the app has reached quiescence before you try this test (eg do it on a button press event, not in viewDidLoad ). 在尝试此测试之前,您需要确保应用程序已达到静止状态(例如,在按钮按下事件上执行此操作,而不是在viewDidLoad )。

But again, this will merely illustrate the futility of the whole exercise. 但同样,这只会说明整个练习的徒劳无益。 You're going to have a really hard time catching the task between the time it started and before it checked the isCancelled property. 在它开始的时间和检查isCancelled属性之前,你将很难捕获任务。 If you really want to manifest the cancel logic in some repeatable manner, we're going to have to artificially make this happen: 如果你真的想以某种可重复的方式表现出取消逻辑,我们将不得不人为地实现这一点:

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()
}

Now, you'd never do this, but it just illustrates that isCancelled does work. 现在,你永远不会这样做,但它只是说明了isCancelled确实有效。

Frankly, you'd never use isCancelled like this. 坦率地说,你永远不会像这样使用isCancelled You would generally use the isCancelled process if doing some long process where you can periodically check the isCancelled status and exit if it is true. 如果执行一些长时间的过程,您通常可以使用isCancelled进程,您可以定期检查isCancelled状态,如果是真,则退出。 But that's not the case in your situation. 但在你的情况下情况并非如此。

The conclusion of all of this is that checking isCancelled at the start of a task is unlikely to ever achieve what you had hoped for. 所有这一切的结论是在任务开始时检查isCancelled不可能实现你所希望的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 iOS-UIViewController的属性navigationController为nil,即使将其设置为UINavigationController的rootViewController - iOS - Property navigationController of UIViewController nil even though it's set as UINavigationController's rootViewController 带有自定义图像的 UIButton 仍然显示 titleLabel,即使我将其设置为空白 - Swift iOS - UIButton with custom image still shows titleLabel even though I set it to blank - Swift iOS Swift iOS-即使存在有效数据,Firebase是否可能损坏数据并返回Nil值? - Swift iOS -Is it Possible For Firebase to Corrupt Data and Return a Nil Value Even Though Valid Data Exists? 即使陈述不正确,Swift Segue仍在运行 - Swift segue running even though if statment is not true iOS:即使单元格可见,cellForItemAtIndexPath也会返回nil - iOS: cellForItemAtIndexPath returns nil even though cell is visible 在运行expo build:ios时,即使我可以手动登录,也会获得无效的凭据 - when running expo build:ios, getting invalid credentials even though I can sign in manually 即使我使用https连接到Amazon S3,Swift 2 iOS 9也抱怨HTTP - Swift 2 ios 9 complaining about http even though I am using https to connect to amazon s3 即使目录中存在文件也获取nil图像 - Getting nil image even though the file exists in directory 即使在 storyboard 中连接 IB 插座,它们也变得为零 - IB outlets are getting nil even though they are connected in storyboard 一个出口怎么可能是零,即使它已经设置了 - How can an outlet be nil even though it has been set
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM