From 1b9677c82ad4637ab61fa3c74c147334e2701996 Mon Sep 17 00:00:00 2001 From: nt54hamnghi Date: Fri, 1 May 2026 18:03:58 -0400 Subject: [PATCH 1/3] Add an example for spawn_blocking --- src/part-guide/io.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/part-guide/io.md b/src/part-guide/io.md index 5f672fe3..7ad064b4 100644 --- a/src/part-guide/io.md +++ b/src/part-guide/io.md @@ -101,9 +101,28 @@ For the rest of this section, we'll assume you have a mix of latency-sensitive t There are essentially three solutions for running long-running or blocking tasks: use a runtime's built-in facilities, use a separate thread, or use a separate runtime. -In Tokio, you can use [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html) to spawn a task which might block. This works like `spawn` for spawning a task, but runs the task in a separate thread pool which is optimized for tasks which might block (the task will likely run on it's own thread). Note that this runs regular synchronous code, not an async task. That means that the task can't be cancelled (even though it's `JoinHandle` has an `abort` method). Other runtimes provide similar functionality. +In Tokio, you can use [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html) to spawn a task which might block. This works like [`spawn`](https://docs.rs/tokio/latest/tokio/task/fn.spawn.html) for spawning a task, but runs the task in a separate thread pool which is optimized for tasks which might block (the task will likely run on it's own thread). Note that this runs regular synchronous code, not an async task. That means that the task can't be cancelled (even though its `JoinHandle` has an `abort` method). Other runtimes provide similar functionality. -You can spawn a thread to do the blocking work using [`std::thread::spawn`](https://doc.rust-lang.org/stable/std/thread/fn.spawn.html) (or similar functions). This is pretty straightforward. If you need to run a lot of tasks, you'll probably need some kind of thread pool or work scheduler. If you keep spawning threads and have many more than there are cores available, you'll end up sacrificing throughput. [Rayon](https://github.com/rayon-rs/rayon) is a popular choice which makes it easy to run and manage parallel tasks. You might get better performance with something which is more specific to your workload and/or has some knowledge of the tasks being run. +This example uses `spawn_blocking` to perform blocking I/O by calling a synchronous filesystem function from the standard library. Note that `tokio::fs` also exists and provides asynchronous filesystem APIs; however, under the hood it too uses blocking operations wrapped in `spawn_blocking`. This is because on most operating systems, file operations are inherently blocking. + +```rust,norun +use tokio; + +#[tokio::main] +async fn main() { + let contents = tokio::task::spawn_blocking(|| { + std::fs::read_to_string("file.txt").unwrap() + }) + .await + .unwrap(); + + println!("{contents}"); +} +``` + +Because tasks spawned with `spawn_blocking` cannot be aborted, it is intended for tasks that eventually complete. Tasks that may block indefinitely, such as a server listening for incoming requests, are better suited to run on a dedicated thread, to avoid occupying thread from Tokio's blocking thread pool for an extended period. You can spawn a dedicated thread using [`std::thread::spawn`](https://doc.rust-lang.org/stable/std/thread/fn.spawn.html) (or similar functions). This is pretty straightforward. + +If you need to run a lot of tasks, you'll probably need some kind of thread pool or work scheduler. If you keep spawning threads and have many more than there are cores available, you'll end up sacrificing throughput. [Rayon](https://github.com/rayon-rs/rayon) is a popular choice which makes it easy to run and manage parallel tasks. You might get better performance with something which is more specific to your workload and/or has some knowledge of the tasks being run. You can use a separate instances of the async runtime for latency-sensitive tasks and for long-running tasks. This is suitable for CPU-bound tasks, but you still shouldn't use blocking IO, even on the runtime for long-running tasks. For CPU-bound tasks, this is a good solution in that it is the only one which supports the long-running tasks be async tasks. It is also flexible (since the runtimes can be configured to be optimal for the kind of task they're running; indeed, it is necessary to put some effort into runtime configuration to get optimal performance) and lets you benefit from using mature, well-engineered sub-systems like Tokio. You can even use two different async runtimes. In any case, the runtimes must be run on different threads. From a35b081490214ef0a8809c0a62a8f58fbafef2fa Mon Sep 17 00:00:00 2001 From: nt54hamnghi Date: Mon, 4 May 2026 18:23:44 -0400 Subject: [PATCH 2/3] Add an example for using rayon with tokio --- src/part-guide/io.md | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/part-guide/io.md b/src/part-guide/io.md index 7ad064b4..4dd46953 100644 --- a/src/part-guide/io.md +++ b/src/part-guide/io.md @@ -103,7 +103,7 @@ There are essentially three solutions for running long-running or blocking tasks In Tokio, you can use [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html) to spawn a task which might block. This works like [`spawn`](https://docs.rs/tokio/latest/tokio/task/fn.spawn.html) for spawning a task, but runs the task in a separate thread pool which is optimized for tasks which might block (the task will likely run on it's own thread). Note that this runs regular synchronous code, not an async task. That means that the task can't be cancelled (even though its `JoinHandle` has an `abort` method). Other runtimes provide similar functionality. -This example uses `spawn_blocking` to perform blocking I/O by calling a synchronous filesystem function from the standard library. Note that `tokio::fs` also exists and provides asynchronous filesystem APIs; however, under the hood it too uses blocking operations wrapped in `spawn_blocking`. This is because on most operating systems, file operations are inherently blocking. +This example uses `spawn_blocking` to perform blocking I/O by calling a synchronous filesystem function from the standard library. Note that [`tokio::fs`](https://docs.rs/tokio/latest/tokio/fs/index.html) also exists and provides asynchronous filesystem APIs; however, under the hood it too uses blocking operations wrapped in `spawn_blocking`. ```rust,norun use tokio; @@ -112,19 +112,53 @@ use tokio; async fn main() { let contents = tokio::task::spawn_blocking(|| { std::fs::read_to_string("file.txt").unwrap() + println!("{contents}"); }) .await .unwrap(); - - println!("{contents}"); } ``` -Because tasks spawned with `spawn_blocking` cannot be aborted, it is intended for tasks that eventually complete. Tasks that may block indefinitely, such as a server listening for incoming requests, are better suited to run on a dedicated thread, to avoid occupying thread from Tokio's blocking thread pool for an extended period. You can spawn a dedicated thread using [`std::thread::spawn`](https://doc.rust-lang.org/stable/std/thread/fn.spawn.html) (or similar functions). This is pretty straightforward. +Because tasks spawned with `spawn_blocking` cannot be aborted, it is intended for work that eventually completes. Tasks that may block indefinitely, such as a server listening for incoming requests, are better run on a dedicated thread so they do not occupy a thread from Tokio's blocking thread pool for an extended period. You can create one with [`std::thread::spawn`](https://doc.rust-lang.org/stable/std/thread/fn.spawn.html) or a similar API. If you need to run a lot of tasks, you'll probably need some kind of thread pool or work scheduler. If you keep spawning threads and have many more than there are cores available, you'll end up sacrificing throughput. [Rayon](https://github.com/rayon-rs/rayon) is a popular choice which makes it easy to run and manage parallel tasks. You might get better performance with something which is more specific to your workload and/or has some knowledge of the tasks being run. -You can use a separate instances of the async runtime for latency-sensitive tasks and for long-running tasks. This is suitable for CPU-bound tasks, but you still shouldn't use blocking IO, even on the runtime for long-running tasks. For CPU-bound tasks, this is a good solution in that it is the only one which supports the long-running tasks be async tasks. It is also flexible (since the runtimes can be configured to be optimal for the kind of task they're running; indeed, it is necessary to put some effort into runtime configuration to get optimal performance) and lets you benefit from using mature, well-engineered sub-systems like Tokio. You can even use two different async runtimes. In any case, the runtimes must be run on different threads. +Here is an example of using Rayon together with Tokio. It utilizes [`tokio::oneshot::channel`](https://docs.rs/tokio/latest/tokio/sync/oneshot/fn.channel.html) to communicate results between a task spawned by Rayon and the current task in Tokio. + +```rust,norun +use rayon::prelude::*; + +#[tokio::main] +async fn main() { + let data = 0..10; + + let (send, recv) = tokio::sync::oneshot::channel(); + + // Spawn a task on rayon to avoid blocking the current task + rayon::spawn(move || { + // Use rayon's parallel iterators to compute the results in parallel + let results = data.into_par_iter().map(compute).collect::>(); + // Send the result back to Tokio. + send.send(results).expect("Failed to send results to Tokio"); + }); + + // Wait for the rayon task and get the result + let results = recv.await.expect("Panic in rayon::spawn"); + println!("Results: {:?}", results); +} + +fn compute(input: usize) -> usize { + // Simulate a CPU-intensive computation by + // summing up a large number of integers. + let mut sum = 0u64; + for i in 0..100_000_000 { + sum = sum.wrapping_add(i * i); + } + input * 42 +} +``` + +You can use a separate instance of the async runtime for latency-sensitive tasks and for long-running tasks. This is suitable for CPU-bound tasks, but you still shouldn't use blocking IO, even on the runtime for long-running tasks. For CPU-bound tasks, this is a good solution in that it is the only one which supports the long-running tasks be async tasks. It is also flexible (since the runtimes can be configured to be optimal for the kind of task they're running; indeed, it is necessary to put some effort into runtime configuration to get optimal performance) and lets you benefit from using mature, well-engineered sub-systems like Tokio. You can even use two different async runtimes. In any case, the runtimes must be run on different threads. On the other hand, you do need to do a bit more thinking: you must ensure that you are running tasks on the right runtime (which can be harder than it sounds) and communication between tasks can be complicated. We'll discuss synchronisation between sync and async contexts next, but it can be even trickier between multiple async runtimes. Each runtime is it's own little universe of tasks and the schedulers are totally independent. Tokio channels and locks *can* be used from different runtimes (even non-Tokio ones), but other runtimes' primitives may not work in this way. From 4208a984f6370317297127ccdfb15f4e647b3f73 Mon Sep 17 00:00:00 2001 From: nt54hamnghi Date: Fri, 1 May 2026 18:03:58 -0400 Subject: [PATCH 3/3] Fix Tokio blocking and Rayon examples --- src/part-guide/io.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/part-guide/io.md b/src/part-guide/io.md index 4dd46953..14d61b10 100644 --- a/src/part-guide/io.md +++ b/src/part-guide/io.md @@ -112,10 +112,11 @@ use tokio; async fn main() { let contents = tokio::task::spawn_blocking(|| { std::fs::read_to_string("file.txt").unwrap() - println!("{contents}"); }) .await .unwrap(); + + // do something with contents } ``` @@ -130,31 +131,30 @@ use rayon::prelude::*; #[tokio::main] async fn main() { - let data = 0..10; + let data = 1..=10; let (send, recv) = tokio::sync::oneshot::channel(); - // Spawn a task on rayon to avoid blocking the current task rayon::spawn(move || { // Use rayon's parallel iterators to compute the results in parallel let results = data.into_par_iter().map(compute).collect::>(); // Send the result back to Tokio. - send.send(results).expect("Failed to send results to Tokio"); + send.send(results).unwrap(); }); - // Wait for the rayon task and get the result - let results = recv.await.expect("Panic in rayon::spawn"); + // Wait for the rayon task and get the results + let results = recv.await.unwrap(); println!("Results: {:?}", results); } -fn compute(input: usize) -> usize { +fn compute(input: u64) -> u64 { // Simulate a CPU-intensive computation by // summing up a large number of integers. let mut sum = 0u64; for i in 0..100_000_000 { sum = sum.wrapping_add(i * i); } - input * 42 + sum % input } ```