Lan*_*ria 3 grand-central-dispatch ios swift dispatchworkitem
我使用GCD DispatchWorkItem来跟踪我发送到firebase的数据.
我做的第一件事是声明类型的2个类属性,DispatchWorkItem然后当我准备好将数据发送到firebase时,我用值初始化它们.
第一个属性被命名errorTask.当初始化它cancels的firebaseTask,并将它设置nil,然后打印"errorTask炒鱿鱼".它有一个DispatchAsync Timer,将调用它0.0000000001秒如果errorTask没有之前那么取消.
第二个属性被命名firebaseTask.初始化时,它包含一个将数据发送到firebase的函数.如果firebase回调成功,则errorTask取消并设置为nil,然后打印一个打印语句"firebase callback".我还检查firebaseTask是否被取消.
问题是在到达回调errorTask之前始终运行的代码firebaseTask.该errorTask代码取消firebaseTask,将其设为零,但由于某种原因,firebaseTask仍然运行.我想不通为什么?
print语句支持errorTask首先运行的事实,因为
"errorTask fired"之前总是打印出来"firebase callback was reached".
为什么firebaseTask没有被取消并设置为nil,即使errorTask让这些事情发生了?
在我的实际应用程序中,如果用户向Firebase发送一些数据,则会出现活动指示符.达到firebase回调后,活动指示器将被解除,并向用户显示一条提示已成功的警报.但是,如果活动指示器上没有计时器并且从未达到回调,那么它将永远旋转.将DispatchAsyc after计时器设置为15秒,如果未达到回调,则会显示错误标签.它总是有效的10次中的9次.
但每一次都在
firebaseTask 取消并设置为nil,活动指示器将被取消的errorTask代码块驳回actiInd,示出了errorLabel,并取消firebaseTask,并将其设置到零.一旦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")
})
}
Run Code Online (Sandbox Code Playgroud)
这个取消程序没有做我怀疑你认为的那样.取消时DispatchWorkItem,它不执行抢先取消.它肯定与updateChildValues电话无关.它只是执行isCancelled属性的线程安全设置,如果您手动迭代循环,如果您发现任务被取消,您可以定期检查并提前退出.
因此,在isCancelled任务开始时检查并不是非常有用的模式,因为如果尚未创建任务,则没有任何内容可以取消.或者,如果任务已创建并添加到队列中,并在队列有机会启动之前取消,则显然只会取消但从未启动过,您将永远无法进行isCancelled测试.如果任务已经开始,那么isCancelled在cancel调用之前它可能已经过了测试.
最重要的是,尝试对cancel请求进行计时,以便在任务开始之后但在它开始isCancelled测试之前准确接收它们将是徒劳的.你的比赛几乎不可能完美.此外,即使你确实碰巧完美地计时,这也只能说明整个过程是多么无效(只有百万分之一的cancel请求会按照你的意图行事).
通常,如果您要取消异步任务,则将其包装在异步自定义Operation子类中,并实现一个cancel停止基础任务的方法.操作队列只是提供了比调度队列更优雅的模式来取消异步任务.但所有这些都假设底层异步任务提供了一种取消它的机制,我不知道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()
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以播放各种延迟,并查看延迟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()
}
Run Code Online (Sandbox Code Playgroud)
现在,你永远不会这样做,但它只是说明它isCancelled确实有效.
坦率地说,你永远不会这样使用isCancelled.isCancelled如果进行一些长时间的过程,您通常会使用该过程,您可以定期检查isCancelled状态,如果是真,则退出.但在你的情况下情况并非如此.
所有这一切的结论是,isCancelled在任务开始时检查不可能实现您所希望的.
| 归档时间: |
|
| 查看次数: |
2224 次 |
| 最近记录: |