slint

Function spawn_local

source
pub fn spawn_local<F: Future + 'static>(
    fut: F,
) -> Result<JoinHandle<F::Output>, EventLoopError>
Expand description

Spawns a Future to execute in the Slint event loop.

This function is intended to be invoked only from the main Slint thread that runs the event loop.

For spawning a Send future from a different thread, this function should be called from a closure passed to invoke_from_event_loop().

This function is typically called from a UI callback.

§Example

slint::spawn_local(async move {
    // your async code goes here
}).unwrap();

§Compatibility with Tokio and other runtimes

The runtime used to execute the future on the main thread is platform-dependent, for instance, it could be the winit event loop. Therefore, futures that assume a specific runtime may not work. This may be an issue if you call .await on a future created by another runtime, or pass the future directly to spawn_local.

Futures from the smol runtime always hand off their work to separate I/O threads that run in parallel to the Slint event loop.

The Tokio runtime is to the following constraints:

  • Tokio futures require entering the context of a global Tokio runtime.
  • Tokio futures aren’t guaranteed to hand off their work to separate threads and may therefore not complete, because the Slint runtime can’t drive the Tokio runtime.
  • Tokio futures require regular yielding to the Tokio runtime for fairness, a constraint that also can’t be met by Slint.
  • Tokio’s current-thread schedule cannot be used in Slint main thread, because Slint cannot yield to it.

To address these constraints, use async_compat’s Compat::new() to implicitly allocate a shared, multi-threaded Tokio runtime that will be used for Tokio futures.

The following little example demonstrates the use of Tokio’s TcpStream to read from a network socket. The entire future passed to spawn_local() is wrapped in Compat::new() to make it run:

// A dummy TCP server that once reports "Hello World"
use std::io::Write;

let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = listener.local_addr().unwrap();
let server = std::thread::spawn(move || {
    let mut stream = listener.incoming().next().unwrap().unwrap();
    stream.write("Hello World".as_bytes()).unwrap();
});

let slint_future = async move {
    use tokio::io::AsyncReadExt;
    let mut stream = tokio::net::TcpStream::connect(local_addr).await.unwrap();
    let mut data = Vec::new();
    stream.read_to_end(&mut data).await.unwrap();
    assert_eq!(data, "Hello World".as_bytes());
    slint::quit_event_loop().unwrap();
};

// Wrap the future that includes Tokio futures in async_compat's `Compat` to ensure
// presence of a Tokio run-time.
slint::spawn_local(async_compat::Compat::new(slint_future)).unwrap();

slint::run_event_loop_until_quit().unwrap();

server.join().unwrap();

The use of #[tokio::main] is not recommended. If it’s necessary to use though, wrap the call to enter the Slint event loop in a call to tokio::task::block_in_place:

// Wrap the call to run_event_loop to ensure presence of a Tokio run-time.
tokio::task::block_in_place(slint::run_event_loop).unwrap();