Can I use a DispatchSemaphore to control a thread on main queue? - swift

Apparently I can only use DispatchSemaphore if I deal with different queues. But what if I want to run async code on the same queue (in this case the main queue).
let s = DispatchSemaphore(value : 0)
DispatchQueue.main.async {
s.signal()
}
s.wait()
This snippet doesn't work, because the async code is also waiting, because the semaphore blocked the main queue.
Can I do this with semaphore? Or do I need to run the async code on a different queue?
ps. I know I could use sync, instead of async and semaphore in this snippet. But This is just an example code to reproduce an async call.

All of this in on the main thread, so the semaphore.signal() will never be called because the thread will stop on the semaphore.wait() and not continue on.
If you are trying to run some async code and have the main thread wait for it, run that code on a different queue and have it signal the semaphore when it's done, allowing the main thread to continue.

what if I want to run async code on the same queue (in this case the main queue).
Then use DispatchGroup instead. That is not what DispatchSemaphore is for.
Run this code in a playground.
import Foundation
let d = DispatchGroup()
var v:Int = 1
d.enter()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
v = 7
d.leave()
}
d.notify(queue: DispatchQueue.main) {
print("v = \(v)")
}
The output will be v = 7. If you comment out d.enter() and d.leave() then the output will be v = 1.
If I call async, don't I run that code on a different thread?
No, you need to understand thread run loops in general and iOS's Main Event Loop specifically.

Related

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

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)
}

In XCTest: how to test that a function forced execution onto main thread

In the UI class I have a method that accesses UI elements, and hence is supposed to force itself onto a main thread. Here's a minimal example of what I mean:
class SomeUI {
func doWorkOnUI() {
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.doWorkOnUI()
}
return
}
print("Doing the work on UI and running on main thread")
}
}
In the tests, of course there's no problem to test the case when doWorkOnUI() is already running on main thread. I just do this:
func testWhenOnMainThread() {
let testedObject = SomeUI()
let expectation = XCTestExpectation(description: "Completed doWorkOnUI")
DispatchQueue.main.async {
testedObject.doWorkOnUI()
expectation.fulfill()
}
wait(for: [expectation], timeout: 10.0)
// Proceed to some validation
}
That is: force execution onto main thread. Wait for it to complete. Do some checks.
But how to test the opposite case, i.e. ensure that function forced itself to run on main thread when called from the background thread?
For example if I do something like:
...
DispatchQueue.global(qos: .background).async {
testedObject.doWorkOnUI()
expectation.fulfill()
}
...
I just tested that function got executed from the background thread. But I didn't explicitly check that it ran on main thread. Of course, since this function accesses UI elements, the expectation is that it crashes if not forced on main thread. So is "no crash" the only testable condition here? Is there anything better?
When there is an outer closure in the background and an inner closure on the main thread, we want two tests:
Call the outer closure. Do a wait for expectations. Wait for 0.01 seconds. Check that the expected work was performed.
Call the outer closure. This time, don't wait for expectations. Check that the work was not performed.
To use this pattern, I think you'll have to change your code so that the tests can call the outer closure directly without having to do an async dance already. This suggests that your design is too deep to be testable without some changes.
Find a way for an intermediate object to capture the closure. That is, instead of directly calling DispatchQueue.global(qos: .background).async, make a type that represents this action. Then a Test Spy version can capture the closure instead of dispatching it to the background, so that your tests can invoke it directly. Then you can test the call back to main thread using async wait.

Swift DispatchQueue concurrentPerform OpenGL parallel rendering

I have a headless EGL renderer in c++ for Linux that I have wrapped with bindings to use in Swift. It works great – I can render in parallel creating multiple contexts and rendering in separate threads, but I've run into a weird issue. First of all I have wrapped all GL calls specific to a renderer and it's context inside it's own serial queue like below.
func draw(data:Any) -> results {
serial.sync {
//All rendering code for this renderer is wrapped in a unique serial queue.
bindGLContext()
draw()
}
}
To batch data between renderers I used DispatchQueue.concurrentPerform. It works correctly, but when I try creating a concurrent queue with a DispatchGroup something weird happens. Even though I have wrapped all GL calls in serial queues the GL contexts get messed up and all gl calls fail to allocate textures/buffers/etc.
So I am trying to understand the difference between these two and why one works and the other doesn't. Any ideas would be great!
//This works
DispatchQueue.concurrentPerform(iterations: renderers.count) { j in
let batch = batches[j]
let renderer = renderers[j]
let _ = renderer.draw(data:batch)
}
//This fails – specifically GL calls fail
let group = DispatchGroup()
let q = DispatchQueue(label: "queue.concurrent", attributes: .concurrent)
for (j, renderer) in renderers.enumerated() {
q.async(group: group) {
let batch = batches[j]
let _ = renderer.draw(data:batch)
}
}
group.wait()
Edit:
I would make sure the OpenGL wrapper is actually thread safe. Each renderer having it's own serial queue may not help if the multiple renderers are making OpenGL calls simultaneously. It's possible the DispatchQueue.concurrentPerform version works because it is just running serially.
Original answer:
I suspect the OpenGL failures have to do with hitting memory constraints. When you dispatch many tasks to a concurrent queue, GCD doesn't do anything clever to rate-limit the number of tasks that are started. If a bunch of running tasks are blocked doing IO, it may just start more and more tasks before any of them finish, gobbling up more and more memory. Here's a detailed write-up from Mike Ash about the problem.
I would guess that DispatchQueue.concurrentPerform works because it has some kind of extra logic internally to avoid spawning too many threads, though it's not well documented and there may be platform-specific stuff happening here. I'm not sure why the function would even exist if all it was doing was dispatching to a concurrent queue.
If you want to dispatch a large number of items directly to a DispatchQueue, especially if those items have some non-CPU-bound component to them, you need to add some extra logic yourself to limit the number of tasks that get started. Here's an example from Soroush Khanlou's GCD Handbook:
class LimitedWorker {
private let serialQueue = DispatchQueue(label: "com.khanlou.serial.queue")
private let concurrentQueue = DispatchQueue(label: "com.khanlou.concurrent.queue", attributes: .concurrent)
private let semaphore: DispatchSemaphore
init(limit: Int) {
semaphore = DispatchSemaphore(value: limit)
}
func enqueue(task: #escaping () -> ()) {
serialQueue.async(execute: {
self.semaphore.wait()
self.concurrentQueue.async(execute: {
task()
self.semaphore.signal()
})
})
}
}
It uses a sempahore to limit the number of concurrent tasks that are executing on the concurrent queue, and uses a serial queue to feed new tasks to the concurrent queue. Newly enqueued tasks block at self.semaphore.wait() if the maximum number of tasks are already scheduled on the concurrent queue.
You would use it like this:
let group = DispatchGroup()
let q = LimitedWorker(limit: 10) // Experiment with this number
for (j, renderer) in renderers.enumerated() {
group.enter()
q.enqueue {
let batch = batches[j]
let _ = renderer.draw(data:batch)
group.leave()
}
}
group.wait()

DispatchWorkItem not notifying main thread

Note: This is not duplicate question I have already seen Dispatch group - cannot notify to main thread
There is nothing answered about DispatchWorkItem
I have code like below
let dwi3 = DispatchWorkItem {
print("start DispatchWorkItem \(Thread.isMainThread)")
sleep(2)
print("end DispatchWorkItem")
}
let myDq = DispatchQueue(label: "A custom dispatch queue")
dwi3.notify(queue: myDq) {
print("notify")
}
DispatchQueue.global().async(execute: dwi3)
Which is working correctly (I can see notify on console) and not in main thread start DispatchWorkItem false
start DispatchWorkItem false
end DispatchWorkItem
notify
Now I am trying to notify to main thread using
dwi3.notify(queue: DispatchQueue.main) {
print("notify")
}
But it never calls , I have read and found that if Thread is blocked then situation occurs. but i am already executing DisptachWorkItem in DispatchQueue.global()
Please Anyone can help me on this that what actually going on ?
If you are running asynchronous code in a playground then you need to enable indefinite execution, otherwise execution may end before the callbacks execute.
Add the following lines to your code in the playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Once you do this, you will see that the notify executes correctly on the main queue.

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
}
}