Skip to content

Commit 07533a5

Browse files
authored
rt: add Handle::spawn_blocking method (#2501)
This follows a similar pattern to `Handle::spawn` to add the blocking spawn capabilities to `Handle`.
1 parent 4748b25 commit 07533a5

File tree

5 files changed

+83
-5
lines changed

5 files changed

+83
-5
lines changed

tokio/src/runtime/blocking/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ cfg_blocking_impl! {
99

1010
mod schedule;
1111
mod shutdown;
12-
mod task;
12+
pub(crate) mod task;
1313

1414
use crate::runtime::Builder;
1515

tokio/src/runtime/blocking/pool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl fmt::Debug for BlockingPool {
148148
// ===== impl Spawner =====
149149

150150
impl Spawner {
151-
fn spawn(&self, task: Task, rt: &Handle) -> Result<(), ()> {
151+
pub(crate) fn spawn(&self, task: Task, rt: &Handle) -> Result<(), ()> {
152152
let shutdown_tx = {
153153
let mut shared = self.inner.shared.lock().unwrap();
154154

tokio/src/runtime/blocking/schedule.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::runtime::task::{self, Task};
66
///
77
/// We avoid storing the task by forgetting it in `bind` and re-materializing it
88
/// in `release.
9-
pub(super) struct NoopSchedule;
9+
pub(crate) struct NoopSchedule;
1010

1111
impl task::Schedule for NoopSchedule {
1212
fn bind(_task: Task<Self>) -> NoopSchedule {

tokio/src/runtime/blocking/task.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use std::pin::Pin;
33
use std::task::{Context, Poll};
44

55
/// Converts a function to a future that completes on poll
6-
pub(super) struct BlockingTask<T> {
6+
pub(crate) struct BlockingTask<T> {
77
func: Option<T>,
88
}
99

1010
impl<T> BlockingTask<T> {
1111
/// Initializes a new blocking task from the given function
12-
pub(super) fn new(func: T) -> BlockingTask<T> {
12+
pub(crate) fn new(func: T) -> BlockingTask<T> {
1313
BlockingTask { func: Some(func) }
1414
}
1515
}

tokio/src/runtime/handle.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
use crate::runtime::{blocking, context, io, time, Spawner};
22
use std::{error, fmt};
33

4+
cfg_blocking! {
5+
use crate::runtime::task;
6+
use crate::runtime::blocking::task::BlockingTask;
7+
}
8+
49
cfg_rt_core! {
510
use crate::task::JoinHandle;
611

@@ -263,6 +268,79 @@ cfg_rt_core! {
263268
}
264269
}
265270

271+
cfg_blocking! {
272+
impl Handle {
273+
/// Runs the provided closure on a thread where blocking is acceptable.
274+
///
275+
/// In general, issuing a blocking call or performing a lot of compute in a
276+
/// future without yielding is not okay, as it may prevent the executor from
277+
/// driving other futures forward. This function runs the provided closure
278+
/// on a thread dedicated to blocking operations. See the [CPU-bound tasks
279+
/// and blocking code][blocking] section for more information.
280+
///
281+
/// Tokio will spawn more blocking threads when they are requested through
282+
/// this function until the upper limit configured on the [`Builder`] is
283+
/// reached. This limit is very large by default, because `spawn_blocking` is
284+
/// often used for various kinds of IO operations that cannot be performed
285+
/// asynchronously. When you run CPU-bound code using `spawn_blocking`, you
286+
/// should keep this large upper limit in mind; to run your CPU-bound
287+
/// computations on only a few threads, you should use a separate thread
288+
/// pool such as [rayon] rather than configuring the number of blocking
289+
/// threads.
290+
///
291+
/// This function is intended for non-async operations that eventually
292+
/// finish on their own. If you want to spawn an ordinary thread, you should
293+
/// use [`thread::spawn`] instead.
294+
///
295+
/// Closures spawned using `spawn_blocking` cannot be cancelled. When you
296+
/// shut down the executor, it will wait indefinitely for all blocking
297+
/// operations to finish. You can use [`shutdown_timeout`] to stop waiting
298+
/// for them after a certain timeout. Be aware that this will still not
299+
/// cancel the tasks — they are simply allowed to keep running after the
300+
/// method returns.
301+
///
302+
/// Note that if you are using the [basic scheduler], this function will
303+
/// still spawn additional threads for blocking operations. The basic
304+
/// scheduler's single thread is only used for asynchronous code.
305+
///
306+
/// [`Builder`]: struct@crate::runtime::Builder
307+
/// [blocking]: ../index.html#cpu-bound-tasks-and-blocking-code
308+
/// [rayon]: https://docs.rs/rayon
309+
/// [basic scheduler]: fn@crate::runtime::Builder::basic_scheduler
310+
/// [`thread::spawn`]: fn@std::thread::spawn
311+
/// [`shutdown_timeout`]: fn@crate::runtime::Runtime::shutdown_timeout
312+
///
313+
/// # Examples
314+
///
315+
/// ```
316+
/// use tokio::runtime::Runtime;
317+
///
318+
/// # async fn docs() -> Result<(), Box<dyn std::error::Error>>{
319+
/// // Create the runtime
320+
/// let rt = Runtime::new().unwrap();
321+
/// let handle = rt.handle();
322+
///
323+
/// let res = handle.spawn_blocking(move || {
324+
/// // do some compute-heavy work or call synchronous code
325+
/// "done computing"
326+
/// }).await?;
327+
///
328+
/// assert_eq!(res, "done computing");
329+
/// # Ok(())
330+
/// # }
331+
/// ```
332+
pub fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
333+
where
334+
F: FnOnce() -> R + Send + 'static,
335+
R: Send + 'static,
336+
{
337+
let (task, handle) = task::joinable(BlockingTask::new(f));
338+
let _ = self.blocking_spawner.spawn(task, self);
339+
handle
340+
}
341+
}
342+
}
343+
266344
/// Error returned by `try_current` when no Runtime has been started
267345
pub struct TryCurrentError(());
268346

0 commit comments

Comments
 (0)