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;