Swift New Concurrency Framework (Part 3)
It is mandatory to read part2 before continue this
Async Sequence
A type that provides asynchronous, sequential, iterated access to its elements.AsyncSequence is a protocol describing a sequence that can produce elements asynchronously. Its surface API is identical to the Swift standard library’s Sequence, with one difference: You need to await the next element, since it might not be immediately available, as it would in a regular Sequence
ORAsyncSequence
. This protocol allows developers to asynchronously iterate over values coming from a sequence by awaiting them. This means that the sequence can generate or obtain its values asynchronously over time, and provide these values to a for-loop as they become available.
If this sounds familiar, that’s because a Combine publisher does roughly the same thing. A publisher will obtain or generate its values (asynchronously) over time, and it will send these values to subscribers whenever they are available.While the basis of what we can do with both AsyncSequence and Publisher sounds similar, I found this link where you see the roughly difference
Getting Started
As shown in Figure 1 this is synchronous, sequential iterated access to its elements which gives you element in array in sequence , it is not possible that you will get 6 before getting 1 to 5 which means sequential iterated access and synchronous means it will not suspend
This is roughly what the compiler does when building the for-in loop. It first starts off by creating an iterator variable and then uses a while loop to get every item produced by the iterator when next is invoked.
As shown in Figure 2 we start off with a URL to an endpoint.It lists some mock text samples
- Now, normally downloading stuff is really an asynchronous task that can take some time.
- But in this case, we don’t want to wait for all the things to download; instead, we want to show things as they are received so we use the new async/await features to get the lines responded from this endpoint.
- Since the async sequence of lines is emitting each line as it’s received, that means we can potentially have a really large download ahead of us.
- The snippet feels really responsive, and the most awesome part about it is that you can use the same things that you’re familiar with using from regular sequences in this new async context. which we will talk later. That means that you can use the new for-await-in syntax to iterate, and functions like map, dropFirst, filter, and reduce;
- Recap async/await → Async functions let you write concurrent code without the need for callbacks, by using the await keyword.Calling an async function will suspend and then be resumed whenever a value or error is produced.
- AsyncSequence, on the other hand, will suspend on each element and resume when the underlying iterator produces a value or throws.
- as the name implies, they’re just like regular sequences but with a few key differences. 1) each element is delivered asynchronously. 2) But because they are an asynchronous delivery, that means that failure is definitely a possibility.Some async sequences throw, but if failure is not an option, others do not. Just like functions that throw, the compiler will help make sure that errors are handled when iterating or composing.
- asynchronous sequences are a description of how to produce values over time. (In our case when new line available it produce value and give it )
- So an async sequence may be zero or more values and then signify completion with returning a nil from its iterator, just like sequences.
- When an error occurs, that’s also a point at which the async sequence is at a terminal state, and after an error happens, they’ll return nil for any subsequent calls to next on their iterator.
In Figure 1 we saw how compiler basically transform for-in synchronous sequence. to use the new async/await functionality, there is one slight alteration that can be made.It’s as simple as changing that next function to be an asynchronous one.We can now have the iteration participate in Swift concurrency by awaiting the next line.
if the loop was on an async sequence.As previously mentioned, we need to await each item out of the async sequence.This is reflected in the new for-await-in syntax.This all means that if you know how to use Sequence, you already have a good idea on how to use AsyncSequence.
Let’s next take a tour of some of the AsyncSequence APIs that are available as of macOS Monterey, iOS 15, tvOS 15, and watchOS 8.Reading from files is often a prime use case for asynchronous behavior.FileHandle now has a new bytes property that gives access to an asynchronous sequence of bytes from that FileHandle.This can be used in conjunction with the new extension on AsyncSequence that converts asynchronous sequences of bytes into lines.But dealing with files is so common that apple decided that URL should have accessors for both bytes and lines.This is that same API that we used in the initial example.It’s a convenience property on URL to return an AsyncSequence of lines from the contents, either from a file or from the network.
In asynSequence we can use break and continue in the same way we use in normal sequence,
Pretty much anything you can think of for using on Sequence now has an asynchronous counterpart for working with AsyncSequence.
As shown in Figure 7 This second iteration runs sequentially after the iteration of the first loop. Since inside Task context it run sequentially because of await like a normal sequence , You see difference Task vs Task.detached we will later part in detail but for now you can observer Task uses parent context thread which is Main Thread and Task.detached create thread and then work in a sequential manner
- Running code sequentially isn’t always what’s desired.If it’s useful to run the iteration concurrent to other things going on, you can create a new async task that encapsulates the iteration.
- This can be useful when you know the async sequences you’re using might run indefinitely.
- , even though that sequence could potentially be indefinite, it’s considerably less common to occur.But in the world of asynchronous behavior, it is something much more common and something that you need to consider when using them.
Here we can run the two iterations concurrently and terminate the iteration 1 later on. It’s pretty easy with tasks to scope the work of an iteration that might be indefinite to the lifetime of some container.
Create Your Own Async Sequence
What is an AsyncThrowingStream?
You can see an AsyncThrowingStream as a stream of elements that could potentially result in a thrown error. Values deliver over time, and the stream can be closed by a finish event. A finish event could either be a success or a failure once an error occurs.
What is an AsyncStream?
An AsyncStream is similar to the throwing variant but will never result in a throwing error. A non-throwing async stream finishes based on an explicit finished call or when the stream cancels.
As shown in Figure 11 and 12 we created FileDownloader service that download some big image and it gives you progress data how much image is downloaded
The file downloader reports a stream of values during the file download. In this case, it’s reporting a stream of status values to report the current status of the running download. The FileDownloader is a perfect example of a piece of code that you can rewrite to use AsyncStream
As you can see, we wrapped the download method inside an AsyncStream. We describe the stream’s type of value Status
as a generic, allowing us to continue the stream with status updates. On download image bytes by bytes we pass values to our stream utilizer. In the case of the completion handler, we’re either finishing by throwing an error or following up the yield with data with a non-throwing finish callback:
It’s essential to not forget about the finish()
callback after you’ve received the final status update. Otherwise, we will keep the stream alive, and code at the implementation level will never continue.
You can start iterating over the stream of values once you’ve configured your async throwing stream. In the case of our AsyncSequencFileDownloader
example, it will look as follows:
Now I replace url with small size image you see image is download with progress bar
The callback is called on termination of the stream and will tell you whether your stream is still alive or not.
An AsyncStream or AsyncThrowingStream can cancel due to an enclosing task getting canceled. An example could look as follows:. A stream cancels when going out of scope or when the enclosing task cancels. As mentioned before, the cancellation will trigger the onTermination
callback accordingly.
As shown in Figure 18 and 19 we didn’t finish our AsyncStream so as a result onTermination
not called
Summary Of Create Own Async Sequence
- Particularly, there are a few design patterns that work really well with AsyncSequence, Some of those design patterns are like closures that are called multiple times, but also some delegates can work nicely too. Like in previous example Pretty much anything that does not need a response back and is just informing of a new value that occurs can be a prime candidate for making an async sequence.
- These design patterns are really common and you likely already have a few in your apps today.
- This is an example of a common handler pattern.
- It’s a class that has a handler property and a start and stop method.
- It seems like a perfect candidate for AsyncSequence.
Existing usage might be something like this where a monitor is created, and a handler to get values is assigned, and then the monitor is started so that quakes can be sent to the handler.Later on, the monitor might be stopped to cancel the events being produced.We can use the same interface to adapt the usage to the new AsyncStream type.
- It takes only a small bit of code to use it and allows you to build an async sequence.When constructing an async stream, an element type and construction closure is specified.
- The closure has a continuation that can yield values more than once, finish, or handle termination.So this means, in this case, the monitor can be created inside the construction closure.And then the handler can be assigned to yield quakes to the continuation.And then the onTermination can handle the cancellation and cleanup.And then we can start monitoring.
- And this is how the usage of this async stream would work.You can use the powerful transformation functions — like filter — and the new for-await-in syntax.
- This lets you focus on the intent of your code instead of needing to worry about replicating the bookkeeping, since everything is wrapped up into one place.There’s a lot of flexibility with AsyncStream to create your own async sequences.
- This is really just one example and there are likely numerous others that you can adapt in your own code.
- AsyncStream is a great way to adapt your existing code to become an async sequence.
- It handles all of the things you would expect from an async sequence, like safety, iteration, and cancellation; but they also handle buffering.
- AsyncStream is a solid way of building your own async sequences and a suitable return type from your own APIs, since the only source of elements being produced is from the construction.
- And if you need to represent errors being thrown? Well, we have a type for that! AsyncThrowingStream that is just like AsyncStream but can handle errors.
- It offers the same flexibility and safety as AsyncStream but can handle failures by throwing from its iteration.
- AsyncSequence is a really powerful tool that is both safe and familiar for dealing with more than one asynchronous value.
- If you know how to use Sequence, you already know how to use AsyncSequence.
- We’ve gone over what async sequences are, and how they’re used, and introduced you to AsyncStream.
Create Own AsyncSequence using AsyncSequence Protocol
Before moving on please see my blog where we created a LinkedList
from scratch and we conform to Sequence Protocol, to access it as For-in loop , It is highly recommended
Getting Started
- Using AsyncSequence is almost identical to using Sequence, with the exception that your types should conform to AsyncSequence and AsyncIterator, and your next() method should be marked async. When it comes time for your sequence to end, make sure you send back nil from next(), just as with Sequence.
- As you can see in Figure 24, we defined a
DownloadSequenceImages
struct that implements the AsyncSequence protocol. The protocol requires us to return a customAsyncIterator
We can now return self as the iterator and keep all logic centralized. Note that we have to help the compiler by providing the typealias to conform to the AsyncSequence protocol. - The next() method takes care of iterating overall values. Our example comes download image on reverse order to download all images until we reach the end. We implement cancellation support by making checking for Task.isCancelled. It’s very important
- Now that we know what an AsyncSequence is and how it’s implemented under the hood, it’s time to start iterating over the values
- We have to use the
await
keyword since we might receive values asynchronously. We exit the for loop once there are no values to be expected anymore. Implementors of an asynchronous sequence can indicate reaching the limit by returningnil
in thenext()
method. In our case, we’ll reach that point once the counter reaches the configured limit or when the iteration cancels: - Many of the regular Sequence operators are also available for asynchronous sequences. The result is that we can perform operations like mapping and filtering in an asynchronous manner.