zng/task.rs
1//! Parallel async tasks and async task runners.
2//!
3//! Use [`run`], [`respond`] or [`spawn`] to run parallel tasks, use [`wait`], [`io`] and [`fs`] to unblock
4//! IO operations and use [`http`] for async HTTP.
5//!
6//! All functions of this module propagate the [`LocalContext`].
7//!
8//! This crate also re-exports the [`rayon`] and [`parking_lot`] crates for convenience. You can use the
9//! [`ParallelIteratorExt::with_ctx`] adapter in rayon iterators to propagate the [`LocalContext`]. You can
10//! also use [`join`] to propagate thread context for a raw rayon join operation.
11//!
12//! # Examples
13//!
14//! ```
15//! use zng::prelude::*;
16//!
17//! let enabled = var(false);
18//! # let _scope = APP.defaults();
19//! # let _ =
20//! Button! {
21//! on_click = async_hn!(enabled, |_| {
22//! enabled.set(false);
23//!
24//! let sum_task = task::run(async {
25//! let numbers = read_numbers().await;
26//! numbers.par_iter().map(|i| i * i).sum()
27//! });
28//! let sum: usize = sum_task.await;
29//! println!("sum of squares: {sum}");
30//!
31//! enabled.set(true);
32//! });
33//! widget::enabled = enabled;
34//! }
35//! # ;
36//!
37//! async fn read_numbers() -> Vec<usize> {
38//! let raw = task::wait(|| std::fs::read_to_string("numbers.txt").unwrap()).await;
39//! raw.par_split(',').map(|s| s.trim().parse::<usize>().unwrap()).collect()
40//! }
41//! ```
42//!
43//! The example demonstrates three different ***tasks***, the first is a [`UiTask`] in the `async_hn` handler,
44//! this task is *async* but not *parallel*, meaning that it will execute in more then one app update, but it will only execute in the
45//! `on_click` context and thread. This is good for coordinating UI state, like setting variables, but is not good if you want to do CPU intensive work.
46//!
47//! To keep the app responsive we move the computation work inside a [`run`] task, this task is *async* and *parallel*,
48//! meaning it can `.await` and will execute in parallel threads. It runs in a [`rayon`] thread-pool so you can
49//! easily make the task multi-threaded and when it is done it sends the result back to the widget task that is awaiting for it. We
50//! resolved the responsiveness problem, but there is one extra problem to solve, how to not block one of the worker threads waiting IO.
51//!
52//! We want to keep the [`run`] threads either doing work or available for other tasks, but reading a file is just waiting
53//! for a potentially slow external operation, so if we call [`std::fs::read_to_string`] directly we can potentially remove one of
54//! the worker threads from play, reducing the overall tasks performance. To avoid this we move the IO operation inside a [`wait`]
55//! task, this task is not *async* but it is *parallel*, meaning if does not block but it runs a blocking operation. It runs inside
56//! a [`blocking`] thread-pool, that is optimized for waiting.
57//!
58//! # Async IO
59//!
60//! You can use [`wait`], [`io`] and [`fs`] to do async IO, Zng uses this API for internal async IO, they are just a selection
61//! of external async crates re-exported for convenience and compatibility.
62//!
63//! The [`io`] module just re-exports the [`futures-lite::io`] traits and types, adding only progress tracking. The
64//! [`fs`] module is the [`async-fs`] crate. Most of the IO async operations are implemented using extensions traits
65//! so we recommend blob importing [`io`] to start implementing async IO.
66//!
67//! ```
68//! use zng::prelude::*;
69//!
70//! async fn read_numbers() -> Result<Vec<usize>, Box<dyn std::error::Error + Send + Sync>> {
71//! let mut file = task::fs::File::open("numbers.txt").await?;
72//! let mut raw = String::new();
73//! file.read_to_string(&mut raw).await?;
74//! raw.par_split(',').map(|s| s.trim().parse::<usize>().map_err(Into::into)).collect()
75//! }
76//! ```
77//!
78//! All the `std::fs` synchronous operations have an async counterpart in [`fs`]. For simpler one shot
79//! operation it is recommended to just use `std::fs` inside [`wait`], the async [`fs`] types are not async at
80//! the OS level, they only offload operations inside the same thread-pool used by [`wait`].
81//!
82//! # HTTP Client
83//!
84//! You can use [`http`] to implement asynchronous HTTP requests. Zng also uses the [`http`] module for
85//! implementing operations such as loading an image from a given URL, the module is a thin wrapper around the [`isahc`] crate.
86//!
87//! ```
88//! use zng::prelude::*;
89//!
90//! let enabled = var(false);
91//! let msg = var("loading..".to_txt());
92//! # let _scope = APP.defaults();
93//! # let _ =
94//! Button! {
95//! on_click = async_hn!(enabled, msg, |_| {
96//! enabled.set(false);
97//!
98//! match task::http::get_txt("https://httpbin.org/get").await {
99//! Ok(r) => msg.set(r),
100//! Err(e) => msg.set(formatx!("error: {e}")),
101//! }
102//!
103//! enabled.set(true);
104//! });
105//! }
106//! # ;
107//! ```
108//!
109//! For other protocols or alternative HTTP clients you can use [external crates](#async-crates-integration).
110//!
111//! # Async Crates Integration
112//!
113//! You can use external async crates to create futures and then `.await` then in async code managed by Zng, but there is some
114//! consideration needed. Async code needs a runtime to execute and some async functions from external crates expect their own runtime
115//! to work properly, as a rule of thumb if the crate starts their own *event reactor* you can just use then without worry.
116//!
117//! You can use the [`futures`], [`async-std`] and [`smol`] crates without worry, they integrate well and even use the same [`blocking`]
118//! thread-pool that is used in [`wait`]. Functions that require an *event reactor* start it automatically, usually at the cost of one extra
119//! thread only. Just `.await` futures from these crate.
120//!
121//! The [`tokio`] crate on the other hand, does not integrate well. It does not start its own runtime automatically, and expects you
122//! to call its async functions from inside the tokio runtime. After you create a future from inside the runtime you can `.await` then
123//! in any thread, so we recommend manually starting its runtime in a thread and then using the `tokio::runtime::Handle` to start
124//! futures in the runtime.
125//!
126//! External tasks also don't propagate the thread context, if you want access to app services or want to set vars inside external
127//! parallel closures you must capture and load the [`LocalContext`] manually.
128//!
129//! [`LocalContext`]: crate::app::LocalContext
130//! [`isahc`]: https://docs.rs/isahc
131//! [`AppExtension`]: crate::app::AppExtension
132//! [`blocking`]: https://docs.rs/blocking
133//! [`futures`]: https://docs.rs/futures
134//! [`async-std`]: https://docs.rs/async-std
135//! [`smol`]: https://docs.rs/smol
136//! [`tokio`]: https://docs.rs/tokio
137//! [`futures-lite::io`]: https://docs.rs/futures-lite/*/futures_lite/io/index.html
138//! [`async-fs`]: https://docs.rs/async-fs
139//! [`rayon`]: https://docs.rs/rayon
140//! [`parking_lot`]: https://docs.rs/parking_lot
141//!
142//! # Full API
143//!
144//! This module fully re-exports [`zng_task`].
145
146pub use zng_task::{
147 DeadlineError, McWaker, ParallelIteratorExt, ParallelIteratorWithCtx, Progress, ScopeCtx, SignalOnce, UiTask, all, all_ok, all_some,
148 any, any_ok, any_some, block_on, channel, deadline, fs, future_fn, io, join, join_context, poll_respond, poll_spawn, respond, run,
149 run_catch, scope, spawn, spawn_wait, wait, wait_catch, wait_respond, with_deadline, yield_now,
150};
151
152#[cfg(any(doc, feature = "test_util"))]
153pub use zng_task::{doc_test, spin_on};
154
155#[cfg(feature = "http")]
156pub use zng_task::http;
157
158#[cfg(ipc)]
159pub use zng_task::ipc;
160
161#[doc(no_inline)]
162pub use zng_task::{parking_lot, rayon};
163
164pub use zng_app::widget::UiTaskWidget;