Async Channels
Performant channels for Swift concurrency.
Don't communicate by sharing memory; share memory by communicating - Rob Pike
If you are familiar with golang and the go ecosystem, you can skip to the go comparisons section.
Channels are a typed conduit through which you can send and receive values - usually across threads or in this case, Swift async tasks. This library is modeled after go's channel behaviors.
Example
let msg = Channel < String > ( capacity : 3 ) let done = Channel < Bool > ( ) await msg <- " Swift " await msg <- " ❤️ " await msg <- " Channels " msg . close ( ) Task { for await message in msg { print ( message ) } await done <- true } await <- done
Benchmarks
This library vs equivalent go code
Obviously being as fast as go is a lofty goal that we may never reach, but it's still pretty fast!
The above results were sampled from the Int tests in the detailed benchmark results
For more detailed results (on a variety of data types) see the Benchmarks readme.
Usage
Add https://github.com/gh123man/Async-Channels as a Swift package dependency to your project. import AsyncChannels and go!
Channel Operations
Un-Buffered Channels
// Create an un-buffered channel let msg = Channel < String > ( ) Task { // Send a value. Send will suspend until the channel is read. await msg <- " foo " } Task { // Receive a value. Receive will suspend until a value is ready let foo = await <- msg }
Buffered Channels
// Create a buffered channel that can hold 2 items let msg = Channel < String > ( capacity : 2 ) // Writing to a buffered channel will not suspend until the channel is full await msg <- " foo " await msg <- " bar " // Messages are received in the order they are sent print ( await <- msg ) // foo print ( await <- msg ) // bar // The channel is now empty.
Closing Channels
A Channel can be closed. In Swift, the channel receive ( <- ) operator returns T? because a channel read will return nil when the channel is closed. If you try to write to a closed channel, a fatalError will be triggered.
let a = Channel < String > ( ) let done = Channel < Bool > ( ) Task { // a will suspend because there is nothing to receive await <- a await done <- true } // Close will send `nil` causing a to resume in the task above a . close ( ) // done is signaled await <- done
Sequence operations
Channel implements AsyncSequence so you may write:
let a = Channel < String > ( ) for await message in a { print ( message ) }
The loop will break when the channel is closed.
Select
select lets a single task wait on multiple channel operations. select will suspend until at least one of the cases is ready. If multiple cases are ready it will choose one randomly.
Operations
receive(c) receive a value, but do nothing with it.
receive(c) { v in ... } receive a value and do something with it.
send("foo", to: c) send a value and do nothing.
send("foo", to: c) { ... } run some code if a send is successful.
none { ... } if none of the channel operations were ready, none will execute instead.
Examples
let c = Channel < String > ( ) let d = Channel < String > ( ) Task { await c <- " foo " await d <- " bar " } // Will print foo or bar await select { receive ( d ) { print ( $0! ) } receive ( c ) { print ( $0! ) } }
let a = Channel < String > ( capacity : 10 ) let b = Channel < String > ( capacity : 10 ) // Fill up a for _ in ( 0 ..< 10 ) { await a <- " a " } for _ in ( 0 ..< 20 ) { await select { // receive from a and print it receive ( a ) { print ( $0! ) } // send "b" to b send ( " b " , to : b ) // if both a and b suspend, print "NONE" none { print ( " NONE " ) } } }
Code Samples
See the Examples folder for real world usage.
Parallel image converter - Saturate the CPU to convert images applying back pressure to the input.
Notes
If you are looking for a blocking variant of this library for traditional swift concurrency, check out my previous project Swigo which this library is based off of.
Special Thanks
I could not have gotten this far on my own without the help from the folks over at forums.swift.org. Big shout-out and thank you to: