0f72c53fdf
Edit some things, make sure all code runs.
155 lines
6.1 KiB
Markdown
155 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.
|