I'm trying to wait for AVAssetExportSession.exportAsynchronously to complete synchronously, and here's what I've got so far:
func foo() {
let semaphore = DispatchSemaphore(value: 0)
session.exportAsynchronously {
defer {
semaphore.signal()
}
// ...
}
semaphore.wait()
}
I was wondering, is there any possibility that blocking the current thread while exportAsynchronously is running will cause a deadlock?
In other words, is exportAsynchronously guaranteed to run on another DispatchQueue?
Related
I'm having trouble with the below pattern. I need to synchronously wait for an initial async request, where the completion block in turn calls a list of async calls where each of the async calls need to wait for the previous one to end before it can start.
The below code would call all of the nested requests at once, which is not what I want.
let semaphore = DispatchSemaphore.init(value: 0)
self.getThings { (things) -> (Void) in
for thing in things {
self.getSomething { (somevalue) -> (Void) in
}
}
semaphore.signal()
}
semaphore.wait()
So, what I've tried, is to add another semaphore inside the for loop but this has the effect that the nested request are never carried out - it waits indefinitely for semaphore2 signal which never happens. How do I fix this?
let semaphore = DispatchSemaphore.init(value: 0)
self.getThings { (things) -> (Void) in
for thing in things {
let semaphore2 = DispatchSemaphore.init(value: 0)
self.getSomething { (somevalue) -> (Void) in
semaphore2.signal()
}
semaphore2.wait()
}
semaphore.signal()
}
semaphore.wait()
I'd like to efficiently implement this behaviour:
A function is asked to run (by the user). Knowing that this function is also automatically repeatedly called by a timer, I'd like to make sure the function returns whenever it is already running.
In pseudo code:
var isRunning = false
func process() {
guard isRunning == false else { return }
isRunning = true
defer {
isRunning = false
}
// doing the job
}
I am aware of the semaphore concept:
let isRunning = DispatchSemaphore(value: 1)
func process() {
// *but this blocks and then passthru rather than returning immediately if the semaphore count is not zero.
isRunning.wait()
defer {
isRunning.signal()
}
// doing the job
}
How would you use the semaphore to implement this behaviour with a semaphore OR any other solution?
You can use wait(timeout:) with a timeout value of now() to test the
semaphore. If the semaphore count is zero then this returns .timedOut,
otherwise it returns .success (and decreases the semaphore count).
let isRunning = DispatchSemaphore(value: 1)
func process() {
guard isRunning.wait(timeout: .now()) == .success else {
return // Still processing
}
defer {
isRunning.signal()
}
// doing the job
}
I need to perform an async operation for each element in an array, one at at time. This operation calls back on the main queue.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let queue = DispatchQueue(label: "Serial Queue")
queue.sync {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
group.wait()
}
}
print(results) // Never reached
completion()
}
The WebService call isn't calling back - which I think is telling me the main queue is blocked, but I can't understand why.
You should use group.notify() rather than group.wait(), since the latter is a synchronous, blocking operation.
I also don't see a point of dispatching to a queue if you only dispatch a single work item once.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
Maybe it's just a typo but basically don't run the queue synchronously.
Then instead of wait use notify outside(!) of the loop and print the results within the queue.
queue.async {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
I d'ont think your main queue is locked, otherwise you would probably have an infinite loading on your app, as if it crashed ( in MacOS that's for sure ).
Here is what worked for me, maybe it will help :
class func synchronize(completion: #escaping (_ error: Bool) -> Void) {
DispatchQueue.global(qos: .background).async {
// Background Thread
var error = false
let group = DispatchGroup()
synchronizeObject1(group: group){ error = true }
synchronizeObject2(group: group){ error = true }
synchronizeObject3(group: group){ error = true }
group.wait() // will wait for everyone to sync
DispatchQueue.main.async {
// Run UI Updates or call completion block
completion(error)
}
}
}
class func synchronizeObject1(group: DispatchGroup, errorHandler: #escaping () -> Void){
group.enter()
WebservicesController.shared.getAllObjects1() { _ in
// Do My stuff
// Note: if an error occures I call errorHandler()
group.leave()
}
}
If I would say, it may come from the queue.sync instead of queue.async. But I'm not an expert on Asynchronous calls.
Hope it helps
With Swift 3.1 on XCode 8.3, running the following code with the Thread Sanitizer finds a data race (see the write and read comments in the code):
private func incrementAsync() {
let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1 // <--- the write
// Uncomment following line and there's no race, probably because print introduces a barrier
//print("> DispatchWorkItem done")
}
item.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)") // <--- the read
}
DispatchQueue.global(qos: .background).async(execute: item)
}
This seems pretty strange to me as the documentation for the DispatchWorkItem mentions that it allows:
getting notified about their completion
which implies that the notify callback is called once the work item's execution is done.
So I would expect that there would be a happens-before relationship between the DispatchWorkItem's work closure and its notify closure. What would be the correct way, if any, to use a DispatchWorkItem with a registered notify callback like this that wouldn't trigger the Thread Sanitizer error?
I tried registering the notify with item.notify(flags: .barrier, queue: .main) ... but the race persisted (probably because the flag only applies to the same queue, documentation is sparse on what the .barrier flag does). But even calling notify on the same (background) queue as the work item's execution, with the flags: .barrier, results in a race.
If you wanna try this out, I published the complete XCode project on github here: https://github.com/mna/TestDispatchNotify
There's a TestDispatchNotify scheme that builds the app without tsan, and TestDispatchNotify+Tsan with the Thread Sanitizer activated.
Thanks,
Martin
EDIT (2019-01-07): As mentioned by #Rob in a comment on the question, this can't be reproduced anymore with recent versions of Xcode/Foundation (I don't have Xcode installed anymore, I won't guess a version number). There is no workaround required.
Well looks like I found out. Using a DispatchGroup.notify to get notified when the group's dispatched items have completed, instead of DispatchWorkItem.notify, avoids the data race. Here's the same-ish snippet without the data race:
private func incrementAsync() {
let queue = DispatchQueue.global(qos: .background)
let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1
}
let group = DispatchGroup()
group.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)")
}
queue.async(group: group, execute: item)
}
So DispatchGroup introduces a happens-before relationship and notify is safely called after the threads (in this case, a single async work item) finished execution, while DispatchWorkItem.notify doesn't offer this guarantee.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
var job = DispatchWorkItem {
for i in 0..<3 {
DispatchQueue.main.async {
print("job", i)
}
}
DispatchQueue.main.async {
print("job done")
}
}
job.notify(queue: .main) {
print("job notify")
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now(), execute: job)
usleep(100)
job.cancel()
if you guess that this snippet prints out
job 0
job 1
job 2
job done
job notify
you are absolutely right!
increase a deadLine ...
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.01, execute: job)
and you've got
job notify
even though the job executes never
notify has nothing with synchronization of any data captured by DispatchWorkItem's closure.
Let try this example with DispatchGroup!
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let group = DispatchGroup()
group.notify(queue: .main) {
print("group notify")
}
And see the result
group notify
!!! WTF !!! Do you still think you solved the race in your code?
To synchronize any read, write ... use the serial queue, barrier, or semaphore. Dispatch group is totally different beast :-) With dispatch groups you can group together multiple tasks and either wait for them to complete or receive a notification once they complete.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let job1 = DispatchWorkItem {
sleep(1)
DispatchQueue.main.async {
print("job 1 done")
}
}
let job2 = DispatchWorkItem {
sleep(2)
DispatchQueue.main.async {
print("job 2 done")
}
}
let group = DispatchGroup()
DispatchQueue.global(qos: .background).async(group: group, execute: job1)
DispatchQueue.global(qos: .background).async(group: group, execute: job2)
print("line1")
group.notify(queue: .main) {
print("group notify")
}
print("line2")
prints
line1
line2
job 1 done
job 2 done
group notify
I'm learning GCD and got question about semaphore.
Here is my code:
class ViewController: UIViewController {
var semaphore: dispatch_semaphore_t! = nil
override func viewDidLoad() {
super.viewDidLoad()
semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
print("Entering")
self.semaphoreTask()
print(self.semaphore.debugDescription)
}
semaphoreTask()
print(semaphore.debugDescription)
}
func semaphoreTask() {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
for i in 0...1000 {
print(i)
if i == 1000 {
print("i is equal to 10000!")
}
}
dispatch_semaphore_signal(self.semaphore)
}
If I run this code so nothing from semaphoreTask is printed in the console, but if I change
semaphore = dispatch_semaphore_create(0)
to
semaphore = dispatch_semaphore_create(1)
Everything starts work well.
The question is why should I write dispatch_semaphore_create(1) but not 0?
Thank you!
You can use the semaphore in 2 different ways:
To say when work or a resource is ready.
In this case you start the semaphore at 0. The creator calls signal when something is ready. The consumer calls wait to wait for the expected item / resource.
To limit the number of concurrent operations / requests / usages.
In this case you start the semaphore at a positive value, like 4. The users each call wait and if resource is available they are allowed to continue. If not they are blocked. When each has finished with the resource they call signal.
So, what you see it expected because you're setting the semaphore up as a ready flag but using it as an access limit (because you call wait first).
So I corrected my code to show you how I fixed it (thanks to #Wain).
class ViewController: UIViewController {
var semaphore: dispatch_semaphore_t! = nil
override func viewDidLoad() {
super.viewDidLoad()
semaphore = dispatch_semaphore_create(0)
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
print("Entering")
self.semaphoreTask()
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
semaphoreTask()
}
func semaphoreTask() {
print(semaphore.debugDescription)
for i in 0...1000 {
print(i)
if i == 1000 {
print("i is equal to 10000!")
}
}
dispatch_semaphore_signal(self.semaphore)
}
}