rust/doc/tutorial/task.md
Marijn Haverbeke 0f72c53fdf Go over the tutorial again
Edit some things, make sure all code runs.
2012-01-12 13:19:02 +01:00

154 lines
6.1 KiB
Markdown

# Tasks
Rust supports a system of lightweight tasks, similar to what is found
in Erlang or other actor systems. Rust tasks communicate via messages
and do not share data. However, it is possible to send data without
copying it by making use of [unique boxes][uniques], which allow the
sending task to release ownership of a value, so that the receiving
task can keep on using it.
[uniques]: data.html#unique-box
NOTE: As Rust evolves, we expect the Task API to grow and change
somewhat. The tutorial documents the API as it exists today.
## Spawning a task
Spawning a task is done using the various spawn functions in the
module `task`. Let's begin with the simplest one, `task::spawn()`:
let some_value = 22;
let child_task = task::spawn {||
std::io::println("This executes in the child task.");
std::io::println(#fmt("%d", some_value));
};
The argument to `task::spawn()` is a [unique
closure](func.html#unique) of type `fn~()`, meaning that it takes no
arguments and generates no return value. The effect of `task::spawn()`
is to fire up a child task that will execute the closure in parallel
with the creator. The result is a task id, here stored into the
variable `child_task`.
## Ports and channels
Now that we have spawned a child task, it would be nice if we could
communicate with it. This is done by creating a *port* with an
associated *channel*. A port is simply a location to receive messages
of a particular type. A channel is used to send messages to a port.
For example, imagine we wish to perform two expensive computations
in parallel. We might write something like:
# fn some_expensive_computation() -> int { 42 }
# fn some_other_expensive_computation() {}
let port = comm::port::<int>();
let chan = comm::chan::<int>(port);
let child_task = task::spawn {||
let result = some_expensive_computation();
comm::send(chan, result);
};
some_other_expensive_computation();
let result = comm::recv(port);
Let's walk through this code line-by-line. The first line creates a
port for receiving integers:
let port = comm::port::<int>();
This port is where we will receive the message from the child task
once it is complete. The second line creates a channel for sending
integers to the port `port`:
# let port = comm::port::<int>();
let chan = comm::chan::<int>(port);
The channel will be used by the child to send a message to the port.
The next statement actually spawns the child:
# fn some_expensive_computation() -> int { 42 }
# let port = comm::port::<int>();
# let chan = comm::chan::<int>(port);
let child_task = task::spawn {||
let result = some_expensive_computation();
comm::send(chan, result);
};
This child will perform the expensive computation send the result
over the channel. Finally, the parent continues by performing
some other expensive computation and then waiting for the child's result
to arrive on the port:
# fn some_other_expensive_computation() {}
# let port = comm::port::<int>();
some_other_expensive_computation();
let result = comm::recv(port);
## Creating a task with a bi-directional communication path
A very common thing to do is to spawn a child task where the parent
and child both need to exchange messages with each other. The function
`task::spawn_connected()` supports this pattern. We'll look briefly at
how it is used.
To see how `spawn_connected()` works, we will create a child task
which receives `uint` messages, converts them to a string, and sends
the string in response. The child terminates when `0` is received.
Here is the function which implements the child task:
fn stringifier(from_par: comm::port<uint>,
to_par: comm::chan<str>) {
let value: uint;
do {
value = comm::recv(from_par);
comm::send(to_par, uint::to_str(value, 10u));
} while value != 0u;
}
You can see that the function takes two parameters. The first is a
port used to receive messages from the parent, and the second is a
channel used to send messages to the parent. The body itself simply
loops, reading from the `from_par` port and then sending its response
to the `to_par` channel. The actual response itself is simply the
strified version of the received value, `uint::to_str(value)`.
Here is the code for the parent task:
# fn stringifier(from_par: comm::port<uint>,
# to_par: comm::chan<str>) {}
fn main() {
let t = task::spawn_connected(stringifier);
comm::send(t.to_child, 22u);
assert comm::recv(t.from_child) == "22";
comm::send(t.to_child, 23u);
assert comm::recv(t.from_child) == "23";
comm::send(t.to_child, 0u);
assert comm::recv(t.from_child) == "0";
}
The call to `spawn_connected()` on the first line will instantiate the
various ports and channels and startup the child task. The returned
value, `t`, is a record of type `task::connected_task<uint,str>`. In
addition to the task id of the child, this record defines two fields,
`from_child` and `to_child`, which contain the port and channel
respectively for communicating with the child. Those fields are used
here to send and receive three messages from the child task.
## Joining a task
The function `spawn_joinable()` is used to spawn a task that can later
be joined. This is implemented by having the child task send a message
when it has completed (either successfully or by failing). Therefore,
`spawn_joinable()` returns a structure containing both the task ID and
the port where this message will be sent---this structure type is
called `task::joinable_task`. The structure can be passed to
`task::join()`, which simply blocks on the port, waiting to receive
the message from the child task.
## The supervisor relationship
By default, failures in Rust propagate upward through the task tree.
We say that each task is supervised by its parent, meaning that if the
task fails, that failure is propagated to the parent task, which will
fail sometime later. This propagation can be disabled by using the
function `task::unsupervise()`, which disables error propagation from
the current task to its parent.