I've been using DispatchWorkItem
and DispatchQueue
to make async requests in my app. However, I've ran into trouble when trying to abort one of the requests.
I successfully use the workItem.cancel()
to change the flag and I then check it where I want to abort. Like this:
for stop in self.userSettingsController.history {
stop.PassingInfo?.removeAll()
if workItem?.isCancelled ?? false {
print("CANCELED")
workItem = nil
break
}
...
However there's one case where I have no loop in which I can keep checking if the cancelled
flag changes, so I cannot abort the request using the process above. Here's the code:
let tripQueue = DispatchQueue(label: "tripQueue")
var tripWorkItem: DispatchWorkItem? = nil
tripWorkItem = DispatchWorkItem {
self.soapServices.GetPathsByLineAndDirection(lineCode: self.lineCode!, direction: passingInfo.Direction!) { response in
DispatchQueue.main.async {
self.linePaths = response?.filter({$0.Places.contains(where: {$0.Code == self.singleStopSelected?.Code})})
if realTime {
//Get estimated trip
self.showingRealTime = true
if self.linePaths?.count ?? 0 > 0 {
self.getEstimatedTrip(lineCode: self.lineCode ?? "", direction: passingInfo.Direction ?? 0, stopCode: self.singleStopSelected?.Code ?? "", path: (self.linePaths?.first)!) { updateTripTimes in
//Does not work, as is expected. Just showing what I would like to achieve
if tripWorkItem?.isCancelled ?? false {
tripWorkItem = nil
return
}
if updateTripTimes {
DispatchQueue.main.async {
self.updateTripTimes = true
}
}
}
}
} else {
//Get trip
self.showingRealTime = false
self.getTrip(tripId: passingInfo.Id!)
}
}
}
tripWorkItem = nil
}
self.currentTripWorkItem = tripWorkItem
tripQueue.async(execute: tripWorkItem ?? DispatchWorkItem {})
Is there any way to do this?
Thanks in advance.
ps: I'm sorry if this is duplicated, but I searched before and I couldn't find the question. I might be using the wrong terms.
Rather than putting your code in a DispatchWorkItem
, consider wrapping it in a Operation
subclass. You get the same isCancelled
Boolean pattern:
class ComputeOperation: Operation {
override func main() {
while ... {
if isCancelled { break }
// do iteration of the calculation
}
// all done
}
}
For your network request, wrap it in an custom AsynchronousOperation
subclass (eg this implementation ), and implement cancel
which will cancel the network request. For example:
enum NetworkOperationError: Error {
case unknownError(Data?, URLResponse?)
}
class NetworkOperation: AsynchronousOperation {
var task: URLSessionTask!
init(url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
super.init()
self.task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkOperationError.unknownError(data, response)))
self.finish()
}
return
}
DispatchQueue.main.async {
completion(.success(responseData))
self.finish()
}
}
}
override func main() {
task.resume()
}
override func cancel() {
super.cancel()
task.cancel()
}
}
Don't get lost in the details of the above example. Just note that
AsynchronousOperation
;finish
after calling the completion handler; andcancel
implementation cancels the asynchronous task. Then you can define your queue:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4 // use whatever you think is reasonable
And add your operations to it:
let operation = NetworkOperation(url: url) { response in
switch response {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
queue.addOperation(operation)
Now, the issue in your GetPathsByLineAndDirection
and getEstimatedTrip
is that you're not following “cancelable” patterns, namely you don't appear to be returning anything that could be used to cancel the request.
So, let's look at an example. Imagine you had some trivial method like:
func startNetworkRequest(with url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
}
What you'd do is change it to return something that can be canceled, the URLSessionTask
in this example:
@discardableResult
func startNetworkRequest(with url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
return task
}
Now that's an asynchronous task that is cancelable (and you can wrap it with the above AsynchronousOperation
pattern).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.