Reputation: 20341
In swift, what is the common practice to cancel an aync execution?
Using this example, which execute the closure asynchronously, what is the way to cancel the async function?
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.
Upvotes: 1
Views: 1962
Reputation: 437897
Unfortunately, there is no generalized answer to this question as it depends entirely upon your asynchronous implementation.
Let's imagine that your delay
was the typical naive implementation:
static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) {
closure()
}
}
That's not going to be cancelable.
However you can redefine it to use DispatchWorkItem
. This is cancelable:
@discardableResult
static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) -> DispatchWorkItem {
let task = DispatchWorkItem {
closure()
}
DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval, execute: task)
return task
}
By making it return a @discardableResult
, that means that you can use it like before, but if you want to cancel it, grab the result and pass it along. E.g., you can define your asynchronous sum
routine to use this pattern, too:
@discardableResult
func sum(of array: [Int], handler: @escaping (Int) -> Void) -> DispatchWorkItem {
let sum = array.reduce(0, +)
return Globals.delay(3) {
handler(sum)
}
}
Now, doSomething
can, if it wants, capture the returned value and use it to cancel the asynchronously scheduled task:
func doSomething() {
var task = sum(of: [16, 756, 442, 6, 23]) { sum in
print(Date(), sum)
}
...
task.cancel()
}
You can also implement the delay
with a Timer
:
@discardableResult
static func delay(_ timeInterval: TimeInterval, closure: @escaping () -> Void) -> Timer {
Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
closure()
}
}
And
@discardableResult
func sum(of array: [Int], handler: @escaping (Int) -> Void) -> Timer {
let sum = array.reduce(0, +)
return Globals.delay(3) {
handler(sum)
}
}
But this time, you'd invalidate
the timer:
func doSomething() {
weak var timer = sum(of: [16, 756, 442, 6, 23]) { sum in
print(Date(), sum)
}
...
timer?.invalidate()
}
It must be noted that the above scenarios are unique to simple “delay” scenarios. This is not a general purpose solution for stopping asynchronous processes. For example, if the asynchronous tasks consists of some time consuming for
loop, the above is insufficient.
For example, let's say you are doing something really complicated calculation in a for
loop (e.g. processing the pixels of an image, processing frames of a video, etc.). In that case, because there is no preemptive cancelation, you'd need to manually check to see if the DispatchWorkItem
or the Operation
has been canceled by checking their respective isCancelled
properties.
For example, let's consider an operation to sum all primes less than 1 million:
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
}
(Obviously, this isn't an efficient way to solve the “sum of primes less than x” problem, but it just an example for illustrative purposes.)
And
let queue = OperationQueue()
let operation = SumPrimes()
queue.addOperation(operation)
We're not going to be able to cancel
that. Once it starts, there’s no stopping it.
But we can make it cancelable by adding a check for isCancelled
in our loop:
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 { ... }
}
And
let queue = OperationQueue()
let operation = SumPrimes()
queue.addOperation(operation)
...
operation.cancel()
Bottom line, if it’s something other than a simple delay, and you want it to be cancelable, you have to integrate this into your code that can be run asynchronously.
Upvotes: 1
Reputation: 1872
I don't know your algorithm but first I have suggestions for some points.
getSumOf
function for adapt Single Responsibility.reduce
function to sum items in array in better and more efficient way.You can use DispatchWorkItem
to build a cancellable task. So you can remove getSumOf
function and edit doSomething
function like below.
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()
You can also look into OperationQueue for advanced use.
Upvotes: 0
Reputation: 535576
Using this example..., what is the way to cancel the async function?
Using that example, there is no such way. The only way to avoid printing the sum is for self
to go out existence some time in the 0.3 seconds immediately after the call.
(There are ways to make a cancellable timer, but the timer you've made, assuming that it's the delay
I think it is, is not cancellable.)
Upvotes: 0