Swift await/async - how to wait synchronously for an async task to complete? - swift

I'm bridging the sync/async worlds in Swift and doing incremental adoption of async/await. I'm trying to invoke an async function that returns a value from a non async function. I understand that explicit use of Task is the way to do that, as described, for instance, here.
The example doesn't really fit as that task doesn't return a value.
After much searching, I haven't been able to find any description of what I'd think was a pretty common ask: synchronous invocation of an asynchronous task (and yes, I understand that that can freeze up the main thread).
What I theoretically would like to write in my synchronous function is this:
let x = Task {
return await someAsyncFunction()
}.result
However, when I try to do that, I get this compiler error due to trying to access result:
'async' property access in a function that does not support concurrency
One alternative I found was something like:
Task.init {
self.myResult = await someAsyncFunction()
}
where myResult has to be attributed as a #State member variable.
However, that doesn't work the way I want it to, because there's no guarantee of completing that task prior to Task.init() completing and moving onto the next statement. So how can I wait synchronously for that Task to be complete?

You should not wait synchronously for an async task.
One may come up with a solution similar to this:
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
Although it works in some simple conditions, according to WWDC 2021 Swift Concurrency: Behind the scenes, this is unsafe. The reason is the system expects you to conform to a runtime contract. The contract requires that
Threads are always able to make forward progress.
That means threads are never blocking. When an asynchronous function reaches a suspension point (e.g. an await expression), the function can be suspended, but the thread does not block, it can do other works. Based on this contract, the new cooperative thread pool is able to only spawn as many threads as there are CPU cores, avoiding excessive thread context switches. This contract is also the key reason why actors won't cause deadlocks.
The above semaphore pattern violates this contract. The semaphore.wait() function blocks the thread. This can cause problems. For example
func testGroup() {
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 0 ..< 100 {
group.addTask {
syncFunc()
}
}
}
NSLog("Complete")
}
}
func syncFunc() {
let semaphore = DispatchSemaphore(value: 0)
Task {
try? await Task.sleep(nanoseconds: 1_000_000_000)
semaphore.signal()
}
semaphore.wait()
}
Here we add 100 concurrent child tasks in the testGroup function, unfortunately the task group will never complete. In my Mac, the system spawns 4 cooperative threads, adding only 4 child tasks is enough to block all 4 threads indefinitely. Because after all 4 threads are blocked by the wait function, there is no more thread available to execute the inner task that signals the semaphore.
Another example of unsafe use is actor deadlock:
func testActor() {
Task {
let d = Database()
await d.updateSettings()
NSLog("Complete")
}
}
func updateDatabase(_ asyncUpdateDatabase: #Sendable #escaping () async -> Void) {
let semaphore = DispatchSemaphore(value: 0)
Task {
await asyncUpdateDatabase()
semaphore.signal()
}
semaphore.wait()
}
actor Database {
func updateSettings() {
updateDatabase {
await self.updateUser()
}
}
func updateUser() {
}
}
Here calling the updateSettings function will deadlock. Because it waits synchronously for the updateUser function, while the updateUser function is isolated to the same actor, so it waits for updateSettings to complete first.
The above two examples use DispatchSemaphore. Using NSCondition in a similar way is unsafe for the same reason. Basically waiting synchronously means blocking the current thread. Avoid this pattern unless you only want a temporary solution and fully understand the risks.

Other than using semaphore, you can wrap your asynchronous task inside an operation like here. You can signal the operation finish once the underlying async task finishes and wait for operation completion using waitUntilFinished():
let op = TaskOperation {
try await Task.sleep(nanoseconds: 1_000_000_000)
}
op.waitUntilFinished()
Note that using semaphore.wait() or op.waitUntilFinished() blocks the current thread and blocking the thread can cause undefined runtime behaviors in modern concurrency as modern concurrency assumes all threads are always making forward progress. If you are planning to use this method only in contexts where you are not using modern concurrency, with Swift 5.7 you can provide attribute mark method unavailable in asynchronous context:
#available(*, noasync, message: "this method blocks thread use the async version instead")
func yourBlockingFunc() {
// do work that can block thread
}
By using this attribute you can only invoke this method from a non-async context. But some caution is needed as you can invoke non-async methods that call this method from an async context if that method doesn't specify noasync availability.

I wrote simple functions that can run asynchronous code as synchronous similar as Kotlin does, you can see code here. It's only for test purposes, though. DO NOT USE IT IN PRODUCTION as async code must be run only asynchronous
Example:
let result = runBlocking {
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "Some result"
}
print(result) // prints "Some result"

I've been wondering about this too. How can you start a Task (or several) and wait for them to be done in your main thread, for example? This may be C++ like thinking but there must be a way to do it in Swift as well. For better or worse, I came up with using a global variable to check if the work is done:
import Foundation
var isDone = false
func printIt() async {
try! await Task.sleep(nanoseconds: 200000000)
print("hello world")
isDone = true
}
Task {
await printIt()
}
while !isDone {
Thread.sleep(forTimeInterval: 0.1)
}

Related

Is there a way to enforce serial scheduling of async/await calls similar to a GCD serial queue?

Using Swift's new async/await functionality, I want to emulate the scheduling behavior of a serial queue (similar to how one might use a DispatchQueue or OperationQueue in the past).
Simplifying my use case a bit, I have a series of async tasks I want to fire off from a call-site and get a callback when they complete but by design I want to execute only one task at a time (each task depends on the previous task completing).
Today this is implemented via placing Operations onto an OperationQueue with a maxConcurrentOperationCount = 1, as well as using the dependency functionality of Operation when appropriate. I've build an async/await wrapper around the existing closure-based entry points using await withCheckedContinuation but I'm trying to figure out how to migrate this entire approach to the new system.
Is that possible? Does it even make sense or am I fundamentally going against the intent of the new async/await concurrency system?
I've dug some into using Actors but as far as I can tell there's no way to truly force/expect serial execution with that approach.
--
More context - This is contained within a networking library where each Operation today is for a new request. The Operation does some request pre-processing (think authentication / token refreshing if applicable), then fires off the request and moves on to the next Operation, thus avoiding duplicate authentication pre-processing when it is not required. Each Operation doesn't technically know that it depends on prior operations but the OperationQueue's scheduling enforces the serial execution.
Adding sample code below:
// Old entry point
func execute(request: CustomRequestType, completion: ((Result<CustomResponseType, Error>) -> Void)? = nil) {
let operation = BlockOperation() {
// do preprocessing and ultimately generate a URLRequest
// We have a URLSession instance reference in this context called session
let dataTask = session.dataTask(with: urlRequest) { data, urlResponse, error in
completion?(/* Call to a function which processes the response and creates the Result type */)
dataTask.resume()
}
// queue is an OperationQueue with maxConcurrentOperationCount = 1 defined elsewhere
queue.addOperation(operation)
}
// New entry point which currently just wraps the old entry point
func execute(request: CustomRequestType) async -> Result<CustomResponseType, Error> {
await withCheckedContinuation { continuation in
execute(request: request) { (result: Result<CustomResponseType, Error>) in
continuation.resume(returning: result)
}
}
}
A few observations:
For the sake of clarity, your operation queue implementation does not “[enforce] the serial execution” of the network requests. Your operations are only wrapping the preparation of those requests, but not the performance of those requests (i.e. the operation completes immediately, and does not waiting for the request to finish). So, for example, if your authentication is one network request and the second request requires that to finish before proceeding, this BlockOperation sort of implementation is not the right solution.
Generally, if using operation queues to manage network requests, you would wrap the whole network request and response in a custom, asynchronous Operation subclass (and not a BlockOperation), at which point you can use operation queue dependencies and/or maxConcurrentOperationCount. See https://stackoverflow.com/a/57247869/1271826 if you want to see what a Operation subclass wrapping a network request looks like. But it is moot, as you should probably just use async-await nowadays.
You said:
I could essentially skip the queue entirely and replace each Operation with an async method on an actor to accomplish the same thing?
No. Actors can ensure sequential execution of synchronous methods (those without await calls, and in those cases, you would not want an async qualifier on the method itself).
But if your method is truly asynchronous, then, no, the actor will not ensure sequential execution. Actors are designed for reentrancy. See SE-0306 - Actors » Actor reentrancy.
If you want subsequent network requests to await the completion of the authentication request, you could save the Task of the authentication request. Then subsequent requests could await that task:
actor NetworkManager {
let session: URLSession = ...
var loginTask: Task<Bool, Error>?
func login() async throws -> Bool {
loginTask = Task { () -> Bool in
let _ = try await loginNetworkRequest()
return true
}
return try await loginTask!.value
}
func someOtherRequest(with value: String) async throws -> Foo {
let isLoggedIn = try await loginTask?.value ?? false
guard isLoggedIn else {
throw URLError(.userAuthenticationRequired)
}
return try await foo(for: createRequest(with: value))
}
}
Perhaps this is unrelated, but if you are introducing async-await, I would advise against withCheckedContinuation. Obviously, if iOS 15 (or macOS 12) and later, I would use the new async URLSession methods. If you need to go back to iOS 13, for example, I would use withTaskCancellationHandler and withThrowingCheckedContinuation. See https://stackoverflow.com/a/70416311/1271826.

asyncDetached falling back into main thread after MainActor call

I'm trying out the new async/await stuff. My goal here is to run the test() method in the background, so I use Task.detached; but during test() I need to make a call on the main thread, so I'm using MainActor.
(I realize that this may look convoluted in isolation, but it's pared down from a much better real-world case.)
Okay, so test code looks like this (in a view controller):
override func viewDidLoad() {
super.viewDidLoad()
Task.detached(priority: .userInitiated) {
await self.test()
}
}
#MainActor func getBounds() async -> CGRect {
let bounds = self.view.bounds
return bounds
}
func test() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.getBounds()
print("test 2", Thread.isMainThread) // true
}
The first print says I'm not on the main thread. That's what I expect.
But the second print says I am on the main thread. That isn't what I expect.
It feels as if I've mysteriously fallen back into the main thread just because I called a MainActor function. I thought I would be waiting for the main thread and then resuming in the background thread I was already on.
Is this a bug, or are my expectations mistaken? If the latter, how do I step out to the main thread during await but then come back to the thread I was on? I thought this was exactly what async/await would make easy...?
(I can "solve" the problem, in a way, by calling Task.detached again after the call to getBounds; but at that point my code looks so much like nested GCD that I have to wonder why I'm using async/await at all.)
Maybe I'm being premature but I went ahead and filed this as a bug: https://bugs.swift.org/browse/SR-14756.
More notes:
I can solve the problem by replacing
let bounds = await self.getBounds()
with
async let bounds = self.getBounds()
let thebounds = await bounds
But that seems unnecessarily elaborate, and doesn't convince me that the original phenomenon is not a bug.
I can also solve the problem by using actors, and this is starting to look like the best approach. But again, that doesn't persuade me that the phenomenon I'm noting here is not a bug.
I'm more and more convinced that this is a bug. I just encountered (and reported) the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
async {
print("howdy")
await doSomeNetworking()
}
}
func doSomeNetworking() async {
print(Thread.isMainThread)
}
This prints howdy and then the second print prints true. But if we comment out the first print, the remaining (second) print prints false!
How can merely adding or removing a print statement change what thread we're on? Surely that's not intended.
As I understand it, given that this is all very new, there is no guarantee that asyncDetached must schedule off the main thread.
In the Swift Concurrency: Behind the Scenes session, it's discussed that the scheduler will try to keep things on the same thread to avoid context switches. Given that though, I don't know how you would specifically avoid the main thread, but maybe we're not supposed to care as long as the task makes progress and never blocks.
I found the timestamp (23:18) that explains that there is no guarantee that the same thread will pick up a continuation after an await. https://developer.apple.com/videos/play/wwdc2021/10254/?time=1398
Even if you do the testing to figure out the exact threading behavior of awaiting on #MainActor functions, you should not rely on it. As in #fullsailor's answer, the language explicitly does not guarantee that work will be resumed on the same thread after an await, so this behavior could change in any OS update. In the future, you may be able to request a specific thread by using a custom executor, but this is not currently in the language. See https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md for more details.
Further, it hopefully should not cause any problems that you are running on the main thread. See https://developer.apple.com/videos/play/wwdc2021/10254/?time=2074 for details about how scheduling works. You should not be afraid of blocking the main thread by calling a #MainActor function and then doing expensive work afterwards: if there is more important UI work available, this will be scheduled before your work, or your work will be run on another thread. If you are particularly worried, you can use Task.yield() before your expensive work to give Swift another opportunity to move your work off the main thread. See Voluntary Suspension here for more details on Task.yield().
In your example, it is likely that Swift decided that it is not worth the performance hit of context switching back from the main thread since it was already there, but if the main thread were more saturated, you might experience different behavior.
Edit:
The behavior you're seeing with async let is because this spawns a child task which runs concurrently with the work you are doing. Thus, since that child is running on the main thread, your other code isn't. See https://forums.swift.org/t/concurrency-structured-concurrency/41622 for more details on child tasks.
The following formulation works, and solves the entire problem very elegantly, though I'm a little reluctant to post it because I don't really understand how it works:
override func viewDidLoad() {
super.viewDidLoad()
Task {
await self.test2()
}
}
nonisolated func test2() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.view.bounds // access on main thread!
print("test 2", bounds, Thread.isMainThread) // false
}
I've tested the await self.view.bounds call up the wazoo, and both the view access and the bounds access are on the main thread. The nonisolated designation here is essential to ensuring this. The need for this and the concomitant need for await are very surprising to me, but it all seems to have to do with the nature of actors and the fact that a UIViewController is a MainActor.
A brief note that since the question was asked, the UIKit classes got marked with #MainActor, so the code in discussion would print true on both occasions. But the problem can still be reproducible with a "regular" class.
Now, getting back to the dicussed behaviour, it's expected, and as others have said its also logical:
premature optimization is the root of all evil, thread context switches are expensive, so the runtime doesn't easily jump at doing them
the test function is not entirely in a concurrent context, because the code hops between the MainActor and your class, thus, the Swift runtime doesn't know that it has to get back to the cooperative thread pool.
If you convert your class to an actor, you'll see the behaviour you expect. Here's a tweaked actor based on the code from the question:
actor ThreadTester {
func viewDidLoad() {
Task.detached(priority: .userInitiated) {
await self.test()
}
}
#MainActor func getBounds() async -> CGRect {
.zero
}
func test() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.getBounds()
print("test 2", Thread.isMainThread) // true
}
}
Task {
await ThreadTester().viewDidLoad()
}
You can toggle between actor and class, leaving the other code untouched, and you'll consistently see the two behaviours.
Swift's structured concurrency works best if all entities involved in concurrent operations are already part of the structured concurrency family, as in this case the compiler has all the necessary information to make informed decisions.
Checking on which thread task is running is unreliable since swift concurrency may use same thread across multiple tasks to avoid context switches. You have to use actor isolation to make sure your tasks aren't executed on actor (this applies to any actor along with MainActor).
First of all, actors in swift are reentrant. This means whenever you are making an async call actor suspends current task until the method returns and proceeds to execute other tasks submitted. This makes sure actor is never blocked due to a long-running task. So if you are calling any async call inside test method and fear that the method will be executed on main thread then you have nothing to worry about. Since your ViewController class will be MainActor isolated your code becomes:
override func viewDidLoad() {
super.viewDidLoad()
Task {
await self.test()
}
}
func getBounds() -> CGRect {
let bounds = self.view.bounds
return bounds
}
func test() async {
// long running async calls
let bounds = self.getBounds()
// long running async calls
}
Now if some of your long running calls are synchronous then you have to remove test method from MainActor's isolation by applying nonisolated attribute. Also, creating Task with Task.init inherits the current actor context which is then executed on actor, to prevent this you have to use Task.detached to execute the test method:
override func viewDidLoad() {
super.viewDidLoad()
Task.detached {
await self.test()
}
}
func getBounds() -> CGRect {
let bounds = self.view.bounds
return bounds
}
nonisolated func test() async {
// long running async calls
// long running sync calls
let bounds = await self.getBounds()
// long running async calls
// long running sync calls
}
I had the same problem with a function which runs a long task and should always be run on a background thread. Ultimately using the new Swift async await syntax I could not find a easy way based on Task or TaskGroup to ensure this.
A Task.detached call will use a different thread as will the Task call itself. If this is called from the MainThread it will be another thread, if not it can be the main thread.
Ultimately I found a solution which always works - but looks very "hacky".
Make sure your background function is not isolated to the main thread by being part of a class that is a MainActor isolated class (like view controllers)
nonisolated func iAllwaysRunOnBackground() {}
Test for main thread and if executed on the main thread call the function again in a Task, wait for execution and return
nonisolated func iAllwaysRunOnBackground() async throws {
if Thread.isMainThread {
async let newTask = Task {
try await self.iAllwaysRunOnBackground()
}
let _ = try await newTask.value
return
}
function body
}

Swift 4. Wait for async result of HealthKit HKQuery before continuing execution

Problem is how to wait for an async query on HealthKit to return a result BEFORE allowing execution to move on. The returned data is critical for further execution.
I know this has been asked/solved many times and I have read many of the posts, however I have tried completion handlers, Dispatch sync and Dispatch Groups and have not been able to come up with an implementation that works.
Using completion handler
per Wait for completion handler to finish - Swift
This calls a method to run a HealthKit Query:
func readHK() {
var block: Bool = false
hk.findLastBloodGlucoseInHealthKit(completion: { (result) -> Void in
block = true
if !(result) {
print("Problem with HK data")
}
else {
print ("Got HK data OK")
}
})
while !(block) {
}
// now move on to the next thing ...
}
This does work. Using "block" variable to hold execution pending the callback in concept seems not that different from blocking semaphores, but it's really ugly and asking for trouble if the completion doesn't return for whatever reason. Is there a better way?
Using Dispatch Groups
If I put Dispatch Group at the calling function level:
Calling function:
func readHK() {
var block: Bool = false
dispatchGroup.enter()
hk.findLastBloodGlucoseInHealthKit(dg: dispatchGroup)
print ("Back from readHK")
dispatchGroup.notify(queue: .main) {
print("Function complete")
block = true
}
while !(block){
}
}
Receiving function:
func findLastBloodGlucoseInHealthKit(dg: DispatchGroup) {
print ("Read last HK glucose")
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: glucoseQuantity!, predicate: nil, limit: 10, sortDescriptors: [sortDescriptor]) { (query, results, error) in
// .... other stuff
dg.leave()
The completion executes OK, but the .notify method is never called, so the block variable is never updated, program hangs and never exits from the while statement.
Put Dispatch Group in target function but leave .notify at calling level:
func readHK() {
var done: Bool = false
hk.findLastBloodGlucoseInHealthKit()
print ("Back from readHK")
hk.dispatchGroup.notify(queue: .main) {
print("done function")
done = true
}
while !(done) {
}
}
Same issue.
Using Dispatch
Documentation and other S.O posts say: “If you want to wait for the block to complete use the sync() method instead.”
But what does “complete” mean? It seems that it does not mean complete the function AND get the later async completion. For example, the below does not hold execution until the completion returns:
func readHK() {
DispatchQueue.global(qos: .background).sync {
hk.findLastBloodGlucoseInHealthKit()
}
print ("Back from readHK")
}
Thank you for any help.
Yes, please don't fight the async nature of things. You will almost always lose, either by making an inefficient app (timers and other delays) or by creating opportunities for hard-to-diagnose bugs by implementing your own blocking functions.
I am far from a Swift/iOS expert, but it appears that your best alternatives are to use Grand Central Dispatch, or one of the third-party libraries for managing async work. Look at PromiseKit, for example, although I haven't seen as nice a Swift Promises/Futures library as JavaScript's bluebird.
You can use DispatchGroup to keep track of the completion handler for queries. Call the "enter" method when you set up the query, and the "leave" at the end of the results handler, not after the query has been set up or executed. Make sure that you exit even if the query is completed with an error. I am not sure why you are having trouble because this works fine in my app. The trick, I think, is to make sure you always "leave()" the dispatch group no matter what goes wrong.
If you prefer, you can set a barrier task in the DispatchQueue -- this will only execute when all of the earlier tasks in the queue have completed -- instead of using a DispatchGroup. You do this by adding the correct options to the DispatchWorkItem.

Race condition in unit tests

I'm currently testing a number of classes that do network stuff like REST API calls, and a Realm database is mutated in the process. When I run all the different tests I have at once, race conditions appear (but of course, when I run them one by one, they all pass). How can I reliably make the tests pass?
I have tried to call the mentioned functions in a GCD block like this:
DispatchQueue.main.async {
self.function.start()
}
One of my tests are still failing, so I guess the above didn't work. I have enabled Thread Sanitizer and it reports, from time to time, that race conditions appear.
I can't post code, so I'm looking for conceptual solutions.
Typically some form of dependency injection. Be it an internally exposed var to the DispatchQueue, a default argument in a function with the queue, or a constructor argument. You just need some way to pass a test queue that dispatches the event when you need to.
DispatchQueue.main.async will schedule the block async to the callee on the main queue and therefore isn't guarenteed by the time you make an assertion.
Example (disclaimer: I'm typing from memory so it might not compile but it gives the idea):
// In test code.
struct TestQueue: DispatchQueue {
// make sure to impement other necessary protocol methods
func async(block: () -> Void) {
// you can even have some different behavior for when to execute the block.
// also you can pass XCTestExpectations to this TestQueue to be fulfilled if necessary.
block()
}
}
// In source code. In test, pass the Test Queue to the first argument
func doSomething(queue: DispatchQueue = DispatchQueue.main, completion: () -> Void) {
queue.async(block: completion)
}
Other methods of testing async and eliminating race conditions revolve around craftily fulfilling an XCTestExpectation.
If you have access to the completion block that is eventually invoked:
// In source
class Subject {
func doSomethingAsync(completion: () -> Void) {
...
}
}
// In test
func testDoSomethingAsync() {
let subject = Subject()
let expect = expectation(description: "does something asnyc")
subject.doSomethingAsync {
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
// assert something here
// or the wait may be good enough as it will fail if not fulfilled
}
If you don't have access to the completion block it usually means finding a way to inject or subclass a test double that you can set an XCTestExpectation on and will eventually fulfill the expectation when the async work has completed.

Why my NSOperation is not cancelling?

I have this code to add a NSOperation instance to a queue
let operation = NSBlockOperation()
operation.addExecutionBlock({
self.asyncMethod() { (result, error) in
if operation.cancelled {
return
}
// etc
}
})
operationQueue.addOperation(operation)
When user leaves the view that triggered this above code I cancel operation doing
operationQueue.cancelAllOperations()
When testing cancelation, I'm 100% sure cancel is executing before async method returns so I expect operation.cancelled to be true. Unfortunately this is not happening and I'm not able to realize why
I'm executing cancellation on viewWillDisappear
EDIT
asyncMethod contains a network operation that runs in a different thread. That's why the callback is there: to handle network operation returns. The network operation is performed deep into the class hierarchy but I want to handle NSOperations at root level.
Calling the cancel method of this object sets the value of this
property to YES. Once canceled, an operation must move to the finished
state.
Canceling an operation does not actively stop the receiver’s code from
executing. An operation object is responsible for calling this method
periodically and stopping itself if the method returns YES.
You should always check the value of this property before doing any
work towards accomplishing the operation’s task, which typically means
checking it at the beginning of your custom main method. It is
possible for an operation to be cancelled before it begins executing
or at any time while it is executing. Therefore, checking the value at
the beginning of your main method (and periodically throughout that
method) lets you exit as quickly as possible when an operation is
cancelled.
import Foundation
let operation1 = NSBlockOperation()
let operation2 = NSBlockOperation()
let queue = NSOperationQueue()
operation1.addExecutionBlock { () -> Void in
repeat {
usleep(10000)
print(".", terminator: "")
} while !operation1.cancelled
}
operation2.addExecutionBlock { () -> Void in
repeat {
usleep(15000)
print("-", terminator: "")
} while !operation2.cancelled
}
queue.addOperation(operation1)
queue.addOperation(operation2)
sleep(1)
queue.cancelAllOperations()
try this simple example in playground.
if it is really important to run another asynchronous code, try this
operation.addExecutionBlock({
if operation.cancelled {
return
}
self.asyncMethod() { (result, error) in
// etc
}
})
it's because you doing work wrong. You cancel operation after it executed.
Check this code, block executed in one background thread. Before execution start – operation cancel, remove first block from queue.
Swift 4
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .background
let ob1 = BlockOperation {
print("ExecutionBlock 1. Executed!")
}
let ob2 = BlockOperation {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
Swift 2
let operationQueue = NSOperationQueue()
operationQueue.qualityOfService = .Background
let ob1 = NSBlockOperation()
ob1.addExecutionBlock {
print("ExecutionBlock 1. Executed!")
}
let ob2 = NSBlockOperation()
ob2.addExecutionBlock {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
The Operation does not wait for your asyncMethod to be finished. Therefore, it immediately returns if you add it to the Queue. And this is because you wrap your async network operation in an async NSOperation.
NSOperation is designed to give a more advanced async handling instead for just calling performSelectorInBackground. This means that NSOperation is used to bring complex and long running operations in background and not block the main thread. A good article of a typically used NSOperation can be found here:
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
For your particular use case, it does not make sense to use an NSOperation here, instead you should just cancel your running network request.
It does not make sense to put an asynchronous function into a block with NSBlockOperation. What you probably want is a proper subclass of NSOperation as a concurrent operation which executes an asynchronous work load. Subclassing an NSOperation correctly is however not that easy as it should.
You may take a look here reusable subclass for NSOperation for an example implementation.
I am not 100% sure what you are looking for, but maybe what you need is to pass the operation, as parameter, into the asyncMethod() and test for cancelled state in there?
operation.addExecutionBlock({
asyncMethod(operation) { (result, error) in
// Result code
}
})
operationQueue.addOperation(operation)
func asyncMethod(operation: NSBlockOperation, fun: ((Any, Any)->Void)) {
// Do stuff...
if operation.cancelled {
// Do something...
return // <- Or whatever makes senes
}
}