Module ffi_helpers::task
[−]
[src]
Management of asynchronous tasks in an FFI context.
The Task API is very similar to the that exposed by Rust futures, with the aim to be usable from other languages.
The main idea is your C users will:
- Create a
Task
struct (who'srun()
method will execute the job) - Spawn the
Task
on a background thread, receiving an opaqueTaskHandle
through which the task can be monitored - Periodically
poll()
theTaskHandle
to see whether it's done or if there was an error - Retrieve the result when the
Task
completes - destroy the original
TaskHandle
when it's longer need it
Implementing The Task API
To use the Task API you just need to create a struct which implements the
Task
trait. This is essentially just a trait with a run()
function
that'll be given a CancellationToken
.
Implementors of the Task
trait should periodically check the provided
CancellationToken
to see whether the caller wants them to stop early.
Examples
Once you have a type implementing Task
you can use the export_task!()
macro to generate extern "C"
functions for spawning the task and
monitoring its progress. This is usually the most
annoying/error-prone/tedious part of exposing running a Task
in the
background using just a C API.
For this example we're defining a Spin
task which will count up until it
receives a cancel signal, then return the number of spins.
#[derive(Debug, Clone, Copy)] pub struct Spin; impl Task for Spin { type Output = usize; fn run(&self, cancel_tok: &CancellationToken) -> Result<Self::Output, Error> { let mut spins = 0; while !cancel_tok.cancelled() { thread::sleep(Duration::from_millis(10)); spins += 1; } Ok(spins) } } // Generate the various `extern "C"` utility functions for working with the // `Spin` task. The `spawn` function will be called `spin_spawn`, and so on. export_task! { Task: Spin; spawn: spin_spawn; wait: spin_wait; poll: spin_poll; cancel: spin_cancel; cancelled: spin_cancelled; handle_destroy: spin_handle_destroy; result_destroy: spin_result_destroy; } fn main() { // create our `Spin` task let s = Spin; unsafe { // spawn the task in the background and get a handle to it let handle = spin_spawn(&s); assert_eq!(spin_cancelled(handle), 0, "The spin shouldn't have been cancelled yet"); // poll the task. The result can vary depending on the outcome: // - If the task completed, get a pointer to the `Output` // - If it completed with an error, return `null` and update the // LAST_ERROR appropriately // - Return `null` and *don't* set LAST_ERROR if the task isn't done clear_last_error(); let ret = spin_poll(handle); assert_eq!(last_error_length(), 0, "There shouldn't have been any errors"); assert!(ret.is_null(), "The task should still be running"); // tell the task to stop spinning by sending the cancel signal spin_cancel(handle); // wait for the task to finish and retrieve a pointer to its result // Note: this will automatically free the handle, so we don't need // to manually call `spin_handle_destroy()`. let got = spin_wait(handle); assert_eq!(last_error_length(), 0, "There shouldn't have been any errors"); assert!(!got.is_null(), "Oops!"); let num_spins: usize = *got; // don't forget the result is heap allocated so we need to free it spin_result_destroy(got); } }
Managing Task Output Lifetimes
The result of a Task
will be allocated on the heap and then a pointer
returned to the user from the poll
and wait
functions. It is the caller's
responsibility to ensure this gets free'd once you're done with it.
The export_task!()
macro lets you define a results_destroy
function
which will free the object for you.
Zero-sized types (like ()
- Rust's equivalent of the C void
or Python
None
) won't incur an allocation, meaning the results_destroy
function
will be a noop.
Structs
CancellationToken |
A shareable token to let you notify other tasks they should stop what they are doing and exit early. |
Cancelled |
An error to indicate a task was cancelled. |
TaskHandle |
An opaque handle to some task which is running in the background. |
Traits
Task |
A cancellable task which is meant to be run in a background thread. |