Swift iOS -DispatchWorkItem 仍然是 运行,即使它被取消并设置为 Nil
Swift iOS -DispatchWorkItem is still running even though it's getting Cancelled and Set to Nil
我使用 GCD DispatchWorkItem
来跟踪我发送到 firebase 的数据。
我做的第一件事是声明 2 个 class 类型 DispatchWorkItem
的属性,然后当我准备好将数据发送到 firebase 时,我用值初始化它们。
第一个属性被命名为errorTask
。初始化时 cancels
firebaseTask
并将其设置为 nil
然后打印 "errorTask fired"。它有一个 DispatchAsync Timer
,如果在此之前没有取消 errorTask,它将在 0.0000000001 秒内调用它。
第二个 属性 名为 firebaseTask
。初始化时,它包含一个将数据发送到 firebase 的函数。如果 firebase 回调成功,则取消 errorTask
并设置为 nil
,然后打印语句 "firebase callback was reached"。我还检查 firebaseTask 是否被取消。
问题是 errorTask
中的代码总是在到达 firebaseTask
回调之前运行。 errorTask
代码取消了 firebaseTask
并将其设置为 nil 但由于某种原因 firebaseTask
仍然运行。我不明白为什么?
打印语句支持 errorTask 首先运行的事实,因为
"errorTask fired"
总是在 "firebase callback was reached"
.
之前打印
为什么 firebaseTask 没有被取消并设置为 nil,即使 errorTask 使这些事情发生了?
在我的实际应用程序中,如果用户向 Firebase 发送一些数据,则会出现 activity 指示器。一旦达到 firebase 回调,activity 指示器就会消失,并向用户显示一条警告,说明它已成功。但是,如果 activity 指标上没有计时器并且永远不会到达回调,那么它将永远旋转。 DispatchAsyc after
有一个设置为 15 秒的计时器,如果未达到回调,则会显示错误标签。 10 次中有 9 次总是有效。
- 发送数据到FB
- 显示 activity 指标
- 回调已达到,因此取消 errorTask,将其设置为 nil,并关闭 activity 指示器
- 显示成功警报。
但每隔一段时间
- 需要的时间会超过 15 秒
firebaseTask
被取消并设置为 nil,activity 指标将被取消
- 错误标签会显示
- 成功提示仍会出现
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
属性 的 thread-safe 设置,如果您手动迭代循环,则可以定期检查并在发现任务被取消时提前退出.
因此,在任务开始时检查 isCancelled
并不是非常有用的模式,因为如果任务尚未创建,则没有什么可取消的。或者,如果任务已创建并添加到队列中,并在队列有机会启动之前取消,显然它只会被取消但从未启动,您将永远无法进行 isCancelled
测试。如果任务已经开始,它很可能在调用 cancel
之前通过了 isCancelled
测试。
最重要的是,尝试对 cancel
请求进行计时,以便在任务开始后但在进入 isCancelled
测试之前准确接收它们,这将是徒劳的.你有一场几乎不可能完美计时的比赛。此外,即使您碰巧恰好把握了这个时间,这也只能说明整个过程是多么低效(只有百万分之一的 cancel
请求会按您的预期执行)。
通常,如果您有想要取消的异步任务,您会将其包装在一个异步自定义 Operation
子类中,并实施一个 cancel
方法来停止底层任务。操作队列只是提供了比调度队列更优雅的取消异步任务的模式。但所有这些都假定底层异步任务提供了一种取消它的机制,我不知道 Firebase 是否提供了一种有意义的机制来做到这一点。我当然没有在他们的任何示例中看到它。因此,所有这些可能都没有实际意义。
我建议您远离问题中的特定代码模式,并描述您要完成的任务。让我们不要详述您针对更广泛问题的特定尝试解决方案,而是让我们了解更广泛的目标是什么,然后我们可以讨论如何解决它。
顺便说一句,您的示例中还有其他技术问题。
具体来说,我假设您 运行 在主队列中执行此操作。所以 task.perform()
运行 立即将其放入当前队列。但是当主队列上的 运行ning 完成时,你的 DispatchQueue.main.asyncAfter(...)
只能是 运行。因此,即使您指定了 0.0000000001 秒的延迟,它实际上不会 运行 直到主队列可用(即,在您的 perform
在主队列上完成 运行ning 之后而且你已经通过了 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
状态并在为真时退出。但你的情况并非如此。
所有这些的结论是,在任务开始时检查 isCancelled
不太可能实现您所希望的。
我使用 GCD DispatchWorkItem
来跟踪我发送到 firebase 的数据。
我做的第一件事是声明 2 个 class 类型 DispatchWorkItem
的属性,然后当我准备好将数据发送到 firebase 时,我用值初始化它们。
第一个属性被命名为errorTask
。初始化时 cancels
firebaseTask
并将其设置为 nil
然后打印 "errorTask fired"。它有一个 DispatchAsync Timer
,如果在此之前没有取消 errorTask,它将在 0.0000000001 秒内调用它。
第二个 属性 名为 firebaseTask
。初始化时,它包含一个将数据发送到 firebase 的函数。如果 firebase 回调成功,则取消 errorTask
并设置为 nil
,然后打印语句 "firebase callback was reached"。我还检查 firebaseTask 是否被取消。
问题是 errorTask
中的代码总是在到达 firebaseTask
回调之前运行。 errorTask
代码取消了 firebaseTask
并将其设置为 nil 但由于某种原因 firebaseTask
仍然运行。我不明白为什么?
打印语句支持 errorTask 首先运行的事实,因为
"errorTask fired"
总是在 "firebase callback was reached"
.
为什么 firebaseTask 没有被取消并设置为 nil,即使 errorTask 使这些事情发生了?
在我的实际应用程序中,如果用户向 Firebase 发送一些数据,则会出现 activity 指示器。一旦达到 firebase 回调,activity 指示器就会消失,并向用户显示一条警告,说明它已成功。但是,如果 activity 指标上没有计时器并且永远不会到达回调,那么它将永远旋转。 DispatchAsyc after
有一个设置为 15 秒的计时器,如果未达到回调,则会显示错误标签。 10 次中有 9 次总是有效。
- 发送数据到FB
- 显示 activity 指标
- 回调已达到,因此取消 errorTask,将其设置为 nil,并关闭 activity 指示器
- 显示成功警报。
但每隔一段时间
- 需要的时间会超过 15 秒
firebaseTask
被取消并设置为 nil,activity 指标将被取消- 错误标签会显示
- 成功提示仍会出现
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
属性 的 thread-safe 设置,如果您手动迭代循环,则可以定期检查并在发现任务被取消时提前退出.
因此,在任务开始时检查 isCancelled
并不是非常有用的模式,因为如果任务尚未创建,则没有什么可取消的。或者,如果任务已创建并添加到队列中,并在队列有机会启动之前取消,显然它只会被取消但从未启动,您将永远无法进行 isCancelled
测试。如果任务已经开始,它很可能在调用 cancel
之前通过了 isCancelled
测试。
最重要的是,尝试对 cancel
请求进行计时,以便在任务开始后但在进入 isCancelled
测试之前准确接收它们,这将是徒劳的.你有一场几乎不可能完美计时的比赛。此外,即使您碰巧恰好把握了这个时间,这也只能说明整个过程是多么低效(只有百万分之一的 cancel
请求会按您的预期执行)。
通常,如果您有想要取消的异步任务,您会将其包装在一个异步自定义 Operation
子类中,并实施一个 cancel
方法来停止底层任务。操作队列只是提供了比调度队列更优雅的取消异步任务的模式。但所有这些都假定底层异步任务提供了一种取消它的机制,我不知道 Firebase 是否提供了一种有意义的机制来做到这一点。我当然没有在他们的任何示例中看到它。因此,所有这些可能都没有实际意义。
我建议您远离问题中的特定代码模式,并描述您要完成的任务。让我们不要详述您针对更广泛问题的特定尝试解决方案,而是让我们了解更广泛的目标是什么,然后我们可以讨论如何解决它。
顺便说一句,您的示例中还有其他技术问题。
具体来说,我假设您 运行 在主队列中执行此操作。所以 task.perform()
运行 立即将其放入当前队列。但是当主队列上的 运行ning 完成时,你的 DispatchQueue.main.asyncAfter(...)
只能是 运行。因此,即使您指定了 0.0000000001 秒的延迟,它实际上不会 运行 直到主队列可用(即,在您的 perform
在主队列上完成 运行ning 之后而且你已经通过了 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
状态并在为真时退出。但你的情况并非如此。
所有这些的结论是,在任务开始时检查 isCancelled
不太可能实现您所希望的。