Swift async/await, Structured concurrency, @asyncHandler

Swift Concurrency Roadmap

Photo by Fons Heijnsbroek on Unsplash

In 5 October 2020 Swift team published Swift Concurrency Roadmap. Nowadays we are using basic patterns that protect our data with queues instead of locking. Lock-free operations dispatched on independent queues and returning their results through callbacks. Let’s look at this with an example.

Main motivation in this example completion handlers are suboptimal. Because of this there are some problems. We can list problems like this.

Problem 1: Pyramid of doom

Problem 2: Error handling

Problem 3: Conditional execution is hard and error-prone

Problem 4: Many mistakes are easy to make

Problem 5: Completion handlers are awkward, too many APIs are defined synchronously

Problem 6: Other “resumable” computations are awkward to define

We are not investigating this problem in this article but you can read this article for the proposal of Async/Await.

In Swift Concurrency Roadmap Swift team introduced features in two phases.

The first phase introduces the async syntax and actor types; The second phase will enforce full actor isolation, eliminating data races.

Proposals for the First Phase

We are focused on async/await, Structured Concurrency, and Async handlers in this article. Before we start we need to making some adjustments to get the project ready

1. Downloading latest Snapshot

https://swift.org/download/#snapshots

2. Install the latest main snapshot of swift.

3. Set Swift Development Snapshot in toolchain. We can achieve this in 2 ways.

Or Xcode -> Preference -> Components > Toolchains

4. Creating a project. We are working on the SwiftPM in this article so let’s create our project.

5. Setting the required flag for our project. We have to add -Xfrontend -enable-experimental-concurrency flag to our project.

If you want to try this on a project you should add this flag like this.

We are ready to go.

Concurrency Example

First things first we need an entry point for calling async function in a non-async function. runAsyncAndBlock is going to our entryPoint for this.

As you can see we importing _Concurrency, underscore at the beginning of Concurrency meaning it is experimental, not suitable for product and the API might be changed in the future.

Let’s create a login scenario.

We have 2 async func and execution does not continue until these functions return a value. Output of this execution is:

But what if we already have functions that have completion like below.

Well there are helper functions that convert your functions that have completion.

We are skipping withCheckedContinuation and withCheckedThrowingContinuation for now but don’t worry i will talk about these 2 functions later. So we can edit our first login function like this.

We can convert the login function to a throwing function using withUnsafeThrowingContinuation.

And ofcourse we should add try? to the beginning of the login function.

What if we want to call async func in a non-async function like this.

Compiler gives an error about this. So we should add @asyncHandler attribute to non-async function

Note: @asyncHandler functions cannot be async function

We are using func sleep(_: UInt32) -> UInt32 method to instead of this we can write our own sleep for good approach.

Replace all sleep(:) function with our new sleep function.

So as i promise now we can talk about withCheckedContinuation and withCheckedThrowingContinuation. Before we do last read again the definition of withUnsafeContinuation and withUnsafeThrowingContinuation.

The operation functions must resume the continuation *exactly once*.

Exactly Once? What if I call multiple times or never call a resume? Well in CheckedContinuation description:

So CheckedContinuation is a wrapper class for `UnsafeContinuation` that logs misuses of the continuation, logging a message if the continuation is resumed multiple times, or if an object is destroyed without its continuation ever being resumed.

We can use CheckedContinuation instead of UnsafeContinuation for testing purposes. Just replace withUnsafeContinuation to withCheckedContinuation or withUnsafeThrowingContinuation to withCheckedThrowingContinuation.

References

[1] https://forums.swift.org/t/swift-concurrency-roadmap/41611

[2] https://forums.swift.org/t/question-about-async-await/6659/15

[3] https://www.infoq.com/news/2020/11/swift-concurrency-roadmap/

[4] https://desiatov.com/swift-structured-concurrency-introduction/

GİTHUB

https://github.com/gelasdev/swiftConcurrency

Thanks for reading.