Reputation: 267
So I was practicing threading and I have a requirement. I want to wait for the first api call to finish then I want to execute the second one, (Just like await async) if the first one didn't finish I want to stop the code there until it finishes.
So, I wrote this. Tell me if it is correct or if there is any better way to do this
func myFunction() {
var a: Int?
let group = DispatchGroup()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 4) {
a = 1
print("First")
group.leave()
}
group.wait()
group.enter()
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
a = 3
print("Second")
group.leave()
}
// wait ...
group.wait()
group.notify(queue: .main) {
print(a) // y
}
}
Upvotes: 1
Views: 5270
Reputation: 6090
Swift 5.5 and Xcode 13.0 bring async/await, so you can do something like this:
import Foundation
print("Hello, World!")
// Returns a 1 after a 3-second delay
func giveMeAOne() async -> Int {
Thread.sleep(forTimeInterval: 3.0)
return 1
}
// Returns a 5 after a 3-second delay
func giveMeAFive() async -> Int {
Thread.sleep(forTimeInterval: 3.0)
return 5
}
func myFunction() async {
var a: Int = 0
print(a)
a += await giveMeAOne()
print(a)
a += await giveMeAFive()
print(a)
}
async {
await myFunction()
}
sleep(7) // added at the end so that process doesn't exit right away
Note that the above was built as a macOS command-line tool in Xcode, rather than with Swift Playgrounds. As of Xcode 13.0 beta 1, Swift Playgrounds fail on a lot of async/await stuff, unfortunately.
That code above runs them sequentially, and cleans up myFunction quite a bit. Note that this doesn't introduce any concurrency, however. Both giveMeAOne() and giveMeAFive() are run sequentially.
If you wanted to run 4 function calls concurrently, you could do something like this:
/// Returns a random integer from 1 through 10, after a 6-second delay
func randomSmallInt() async -> Int {
Thread.sleep(forTimeInterval: 6)
return Int.random(in: 1 ... 10)
}
/// Fetches 4 random integers, summing them
func myFunction() async {
// print timestamp
print(Date())
async let a = randomSmallInt()
async let b = randomSmallInt()
async let c = randomSmallInt()
async let d = randomSmallInt()
// This awaits for a, b, c, and d to all complete.
// You can only await a value once.
let numbersToAdd = await [a, b, c, d]
var sum = 0
for number in numbersToAdd {
print("adding \(number)")
sum += number
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async {
await myFunction()
}
// make sure we have time to finish before process exits
Thread.sleep(forTimeInterval: 10)
When you run this, you can see from the timestamps that the total execution time only took about 6 seconds -- because all of the async let ...
statements were executed concurrently.
This works great if you have a fixed number of things you need to call asynchronously. But what if you wanted to make 100 asynchronous function calls, perhaps loading images or something?
You may think you could do something like this, which will work, but will run everything synchronously -- probably not what you want:
// Don't do this -- these all run sequentially
var sum = 0
for _ in 0 ..< 100 {
async let value = randomSmallishInt()
let number = await value
print("Adding \(number)")
sum += number
}
If you want to do an arbitrary or unknown number of asynchronous things, the best way it to use a taskGroup
. This code is a bit more verbose than it needs to be, in order to make things clearer. Note: Xcode 13.0 beta struggles with respect to print
statement output, missing much of it. Anyway, here it is:
/// Returns a random integer from 1 through 10
func randomSmallInt() async -> Int {
Thread.sleep(forTimeInterval: 6)
return Int.random(in: 1 ... 10)
}
func myFunction_arbitrary_repetitions_asynchronously() async {
// print timestamp
print(Date())
// This is pretty cool --
// `withTaskGroup` creates a context for executing a group of async tasks
// In this case, `Int.self` specifies that each individual task will return
// an Int. `[Int].self` specifies that the entire group will return an array of Ints:
let numbers = await withTaskGroup(of: Int.self, returning: [Int].self) { group in
// Repeat 100 times
for _ in 0 ..< 100 {
// Run the next thing asynchronously
group.async {
return await randomSmallInt()
}
}
// Iterate through results
var result: [Int] = []
for await individualResult in group {
result.append(individualResult)
}
return result
}
var sum = 0
for number in numbers {
print("Adding \(number)")
sum += number
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async { await myFunction_arbitrary_repetitions_asynchronously() }
Thread.sleep(10)
You could shorten this up considerably:
func myFunction_arbitrary_repetitions_asynchronously_short() async {
// print timestamp
print(Date())
// In this case, the taskGroup just returns the sum
let sum = await withTaskGroup(of: Int.self, returning: Int.self) { group in
// Repeat 100 times
for _ in 0 ..< 100 {
// Run the next thing asynchronously
group.async {
return await randomSmallInt()
}
}
// Iterate through results
var sum = 0
for await individualResult in group {
print("Adding \(individualResult)")
sum += individualResult
}
return sum
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async { await myFunction_arbitrary_repetitions_asynchronously_short() }
Important: Xcode 13.0 beta 1 gets quite unstable when dealing with this code. Specifically, the debugger likes to detach when the process isn't finished yet. For example, the output of many of the print
statements does not appear, and using breakpoints is tough with async code. Building a command-line tool and then running the resulting executable (found in ~/Library/Developer/Xcode/DerivedData//Build/Products/Debug) will at least show all the output of your print statements.
Upvotes: 4
Reputation: 534958
Just like await async
The lack of a built-in language-level await async
mechanism is a serious issue for Swift, and will probably be remedied in a future version. In the meantime, Swift has basically no built-in language-level mechanism for threading; you have to deal with things manually, by talking to either Cocoa/Objective-C (Grand Central Dispatch, NSOperation) or some other framework that deals with asynchronicity (Combine).
The particular problem you've focused on is what I call serializing asynchronicity. There are several ways to do this.
The simple way is, as you've been told, perform the second async call at the end of the execution handler of the first async call. This is not very general for lining up numerous tasks, obviously.
You can certainly use DispatchGroup in the way you have outlined. Your statement of the pattern is almost correct. You do not need the last wait
, and, more important, you must get onto a background queue right at the start, because using DispatchGroup on the main queue is illegal; your code, as it stands, effectively block the main queue while we wait, which is something you must not do.
You can make a serial OperationQueue and load Operation objects onto it. This is a much more general mechanism. You can make Operation objects depend upon one another. Before the arrival of the Combine framework, this was the best general solution.
If you can confine yourself to iOS 13 and later, the Combine framework is the neatest way to serialize asynchronicity in a general way. See for further discussion Combine framework serialize async operations.
Upvotes: 1
Reputation: 285069
Don't wait.
Run the second API call in the completion handler of the first
func myFunction() {
var a = 0
DispatchQueue.global().asyncAfter(deadline: .now() + 4) {
a = 1
print("First")
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
a = 3
print("Second")
DispatchQueue.main.async {
print(a)
}
}
}
}
Upvotes: 1