Given the code below from a class:
cancellable = input
.receive(on: scheduler)
.map { ... }
.sink(receiveValue: { value in
self.state = value
})
where input is a PassthroughSubject.
Now, when scheduler is the main queue or the RunLoop.main AND input will be called from the main thread, does receive(on: scheduler) programmatically optimise away an explicit dispatch to the main queue?
So, basically something like this:
if Thread.isMainThread {
/* execute closure */
} else {
/* dispatch closure async to main */
}
The documentation for receive(on:) gives a vague hint, that it might perform some optimisations:
"Prefer receive(on:options:) over explicit use of dispatch queues"
pub.sink {
DispatchQueue.main.async {
// Do something.
}
}
No, receive(on:) does not optimize away the dispatch. Doing so could lead to a deadlock. Example:
let l = Lock()
let cancellable = input
.receive(on: scheduler)
.map { ... }
.sink(receiveValue: { value in
l.lock()
self.state = value
l.unlock()
})
l.lock()
input.send(1)
l.unlock()
If the dispatch were eliminated, this example would try to lock the already-locked lock l, and hang (or crash if it can detect the deadlock).
Looking at the sources for RunLoop and DispatchQueue, it doesn't look like there is such an optimisation in their conformance to the Scheduler protocol.
But to be fair, there might be lower level optimisations at play.
Related
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)
}
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.
PromiseKit provides a convenience method thenOn for running parts of your chain on non-main threads. However there doesn't appear to be any convenient way of setting the first promise execution thread.
This means that I end up either placing a DispatchQueue.global(x).async inside my first promise, or I use a dummy first promise.
Placing the DispatchQueue bit in my first promise feels broken, I'm moving the threading decision from the main execution chain to the individual promise, but just for that one promise. If I later prepend a promise to my chain, I have to move all that threading logic around... not good.
What i've been doing lately is this:
let queue = DispatchQueue.global(qos: .userInitiated)
Promise(value: ()).then(on: queue) {
// Now run first promise function
}
This is definitely a cleaner solution, but I was wondering if anyone knew of an
even better solution... I'm sure this isn't a rare scenario after all?
We provide:
DispatchQueue.global().promise {
//…
}
firstly does not take a queue because it executes its contents immediately for the default case and thus allowing it to have a configured queue would make its behavior confusingly variable.
UPDATE: At the latest version it looks like
DispatchQueue.global().async(.promise) {
//...
}
You can perform your operation on any queue, just call fulfill or reject from it
Look at example:
override func viewDidLoad() {
super.viewDidLoad()
firstly {
doFirstly()
}
.then {
self.doMain()
}
.then {
self.doLastest()
}
.catch { error in
// handle error
}
.always {
// finish
}
}
func doFirstly() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
// do your operation here
fulfill()
}
}
}
func doMain() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.main
queue.async {
// do your operation here
fulfill()
}
}
}
func doLastest() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
// do your operation here
fulfill()
}
}
}
I’m executing some functions in a test asynchronously using a DispatchQueue like this:
let queue: DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
let group: DispatchGroup = DispatchGroup()
func execute(argument: someArg) throws {
group.enter()
queue.async {
do {
// Do stuff here
group.leave()
} catch {
Log.info(“Something went wrong")
}
}
group.wait()
}
Sometimes the code inside the do block can throw errors, that I have to catch later on. Since I’m developing a test, I want it to fail, if the code inside the do block throws an error.
Is there a way to throw an error, without catching it inside the queue.async call?
You cannot throw an error, but you can return an error:
First, you need to make your calling function asynchronous as well:
func execute(argument: someArg, completion: #escaping (Value?, Error?)->()) {
queue.async {
do {
// compute value here:
...
completion(value, nil)
} catch {
completion(nil, error)
}
}
}
The completion handler takes a parameter which we could say is a "Result" containing either the value or an error. Here, what we have is a tuple (Value?, Error?), where Value is the type which is calculated by the task. But instead, you could leverage a more handy Swift Enum for this, e.g. Result<T> or Try<T> (you might want to the search the web).
Then, you use it as follows:
execute(argument: "Some string") { value, error in
guard error == nil else {
// handle error case
}
guard let value = value else {
fatalError("value is nil") // should never happen!
}
// do something with the value
...
}
Some rules that might help:
If a function calls an asynchronous function internally, it inevitable becomes an asynchronous function as well. *)
An asynchronous function should have a completion handler (otherwise, it's some sort of "fire and forget").
The completion handler must be called, no matter what.
The completion handler must be called asynchronously (with respect the the caller)
The completion handler should be called on a private execution context (aka dispatch queue) unless the function has a parameter specifying where to execute the completion handler. Never use the main thread or main dispatch queue - unless you explicitly state that fact in the docs or you intentionally want to risk dead-locks.
*) You can force it to make it synchronous using semaphores which block the calling thread. But this is inefficient and really rarely needed.
Well, you might conclude, that this looks somewhat cumbersome. Fortunately, there's help - you might look for Future or Promise which can nicely wrap this and make the code more concise and more comprehensible.
Note: In Unit Test, you would use expectations to handle asynchronous calls (see XCTest framework).
Refactor your code to use queue.sync and then throw your error from there. (Since your execute function is actually synchronous, given the group.wait() call at the last line, it shouldn't really matter.)
For instance, use this method from DispatchQueue:
func sync<T>(execute work: () throws -> T) rethrows -> T
By the way, a good idiom for leaving a DispatchGroup is:
defer { group.leave() }
as the first line of your sync/async block, which guarantees you won't accidentally deadlock when an error happens.
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
}
}