Channel
Standard library class · Written Channel[Int](capacity: 2), with its type in an annotation
A Channel carries values from one task to another. One task sends into it and another receives from
it, and each waits for the other when it has to. It is how tasks hand work on without sharing a var.
Tasks.run { tasks => const numbers: Channel[Int] = Channel(capacity: 2)
tasks.start { => for n in 1..5 { numbers.send(n) } numbers.close() }
var total = 0 for n in numbers { total += n } print("total: #{total}")}total: 15One task sends the numbers 1 to 5 and then closes the channel to say there are no more. The group’s own
block receives with a for loop, which keeps going until the channel is closed and empty.
A channel has a type for what it carries, and every value sent must match it. The type is written in an
annotation on the name, as Channel[Int] above, because there is nothing in Channel(capacity: 2) to
say what will be sent. Messages go through in the order they were sent, and a value that is sent is the
same kind of copy as anywhere else in Emerald: lists, dictionaries, strings, and structs are copied, and
an object of a class is shared.
A channel is shared by every task that names it, not copied.
At a glance
Section titled “At a glance”| Making one | |
|---|---|
Channel(capacity:) |
A channel, with room for some waiting messages |
| Passing values | |
|---|---|
send(value) |
Put a value in, waiting for room if needed |
receive() |
Take the next value, or nothing once the channel is finished |
for item in channel |
Take every value until the channel is finished |
| Finishing | |
|---|---|
close() |
Say that nothing more will be sent |
Making one
Section titled “Making one”Channel(capacity: Int = 0): Channel[T]
A channel for values of type T. capacity is how many messages can wait in it. With the default of
zero, a send waits until a task is ready to receive, so sender and receiver meet:
Tasks.run { tasks => const words: Channel[String] = Channel() tasks.start { => print("sending") words.send("hello") print("sent") } print("receiving") print(words.receive())}receivingsendingsenthelloWith a positive capacity, a sender can put that many messages in and carry on, and then waits for a receiver to make room. Senders wait in the order they arrived, and so do receivers. A small capacity keeps a fast sender from running far ahead of a slow receiver.
The values a channel carries can’t themselves be optional, because nothing is how receive says the
channel is finished. To send something that might be missing, put it in a field of a struct:
struct Reading { const sensor: String const value: Int?}
const readings: Channel[Reading] = Channel(capacity: 4)A Channel[Int?] is refused before the program runs, with the advice to wrap the value in a struct.
Raises a RuntimeError for a negative capacity.
Passing values
Section titled “Passing values”Puts a value into the channel. If the channel has no room, it waits, until a receiver takes a message or makes room.
const queue: Channel[String] = Channel(capacity: 1)queue.close()
try { queue.send("too late")}catch error: RuntimeError { print(error.message)}cannot send to a closed channelRaises a RuntimeError when the channel is closed. A message that was already handed over stays
delivered. It can also raise CancelledError while waiting, and
DeadlockError when nothing could ever take the message.
Takes the next message, waiting for one if there is none yet. When the channel has been closed and
nothing is left in it, it gives nothing, so a loop can tell that the sending is over.
Tasks.run { tasks => const words: Channel[String] = Channel(capacity: 2) tasks.start { => words.send("one") words.send("two") words.close() }
var word = words.receive() while word != nothing { print(word) word = words.receive() } print("channel finished")}onetwochannel finishedA for loop does exactly this for you: for item in channel { ... } receives until the channel is
closed and empty. It is usually the clearest way to read a channel.
Raises CancelledError while waiting, if the task is cancelled, and
DeadlockError when nothing could ever send.
Finishing
Section titled “Finishing”Says that nothing more will be sent. Messages already in the channel can still be received, and every
task waiting to receive wakes up and gets nothing. Closing a channel that is already closed does
nothing, so it is safe to close one twice.
const queue: Channel[Int] = Channel(capacity: 1)queue.send(7)queue.close()print(queue.receive())print(queue.receive())7nothingClose a channel from the side that sends, once it is done, so that the receivers know to stop.