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