فهرست منبع

feat(core): `async_runtime` `handle` API, `spawn` returns `JoinHandle` (#2399)

Lucas Fernandes Nogueira 4 سال پیش
والد
کامیت
9aeb04faf4

+ 5 - 0
.changes/async-runtime-handle-api.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Add `handle` API to `tauri::async_runtime`.

+ 5 - 0
.changes/async-runtime-spawn-returns-join-handle.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+**Breaking change:** The `tauri::async_runtime::spawn` function now returns `tauri::async_runtime::JoinHandle<T>`.

+ 3 - 1
core/tauri/Cargo.toml

@@ -69,7 +69,8 @@ os_pipe = { version = "0.9", optional = true }
 rfd = "0.4.2"
 raw-window-handle = { version = "0.3.3", optional = true }
 minisign-verify = { version = "0.1", optional = true }
-os_info = { version = "3.0.6", optional = true}
+os_info = { version = "3.0.6", optional = true }
+futures-lite = "1.12"
 
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
 gtk = { version = "0.14", features = [ "v3_20" ] }
@@ -86,6 +87,7 @@ serde = { version = "1.0", features = [ "derive" ] }
 quickcheck = "1.0.3"
 quickcheck_macros = "1.0.0"
 tokio-test = "0.4.2"
+tokio = { version = "1.9", features = [ "full" ] }
 mockito = "0.30"
 
 [features]

+ 79 - 6
core/tauri/src/async_runtime.rs

@@ -7,17 +7,73 @@
 //! Fox more complex use cases, consider creating your own runtime.
 //! For command handlers, it's recommended to use a plain `async fn` command.
 
+use futures_lite::future::FutureExt;
 use once_cell::sync::OnceCell;
 use tokio::runtime::Runtime;
-pub use tokio::sync::{
-  mpsc::{channel, Receiver, Sender},
-  Mutex, RwLock,
+pub use tokio::{
+  runtime::Handle,
+  sync::{
+    mpsc::{channel, Receiver, Sender},
+    Mutex, RwLock,
+  },
+  task::JoinHandle as TokioJoinHandle,
 };
 
-use std::future::Future;
+use std::{
+  fmt,
+  future::Future,
+  pin::Pin,
+  task::{Context, Poll},
+};
 
 static RUNTIME: OnceCell<Runtime> = OnceCell::new();
 
+/// An owned permission to join on a task (await its termination).
+#[derive(Debug)]
+pub struct JoinHandle<T>(TokioJoinHandle<T>);
+
+impl<T> Future for JoinHandle<T> {
+  type Output = crate::Result<T>;
+  fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+    self
+      .0
+      .poll(cx)
+      .map_err(|e| crate::Error::JoinError(Box::new(e)))
+  }
+}
+
+/// Runtime handle definition.
+pub trait RuntimeHandle: fmt::Debug + Clone + Sync + Sync {
+  /// Spawn a future onto the runtime.
+  fn spawn<F: Future>(&self, task: F) -> JoinHandle<F::Output>
+  where
+    F: Future + Send + 'static,
+    F::Output: Send + 'static;
+
+  /// Run a future to completion on runtime.
+  fn block_on<F: Future>(&self, task: F) -> F::Output;
+}
+
+impl RuntimeHandle for Handle {
+  fn spawn<F: Future>(&self, task: F) -> JoinHandle<F::Output>
+  where
+    F: Future + Send + 'static,
+    F::Output: Send + 'static,
+  {
+    JoinHandle(self.spawn(task))
+  }
+
+  fn block_on<F: Future>(&self, task: F) -> F::Output {
+    self.block_on(task)
+  }
+}
+
+/// Returns a handle to the async runtime.
+pub fn handle() -> impl RuntimeHandle {
+  let runtime = RUNTIME.get_or_init(|| Runtime::new().unwrap());
+  runtime.handle().clone()
+}
+
 /// Run a future to completion on runtime.
 pub fn block_on<F: Future>(task: F) -> F::Output {
   let runtime = RUNTIME.get_or_init(|| Runtime::new().unwrap());
@@ -25,11 +81,28 @@ pub fn block_on<F: Future>(task: F) -> F::Output {
 }
 
 /// Spawn a future onto the runtime.
-pub fn spawn<F>(task: F)
+pub fn spawn<F>(task: F) -> JoinHandle<F::Output>
 where
   F: Future + Send + 'static,
   F::Output: Send + 'static,
 {
   let runtime = RUNTIME.get_or_init(|| Runtime::new().unwrap());
-  runtime.spawn(task);
+  JoinHandle(runtime.spawn(task))
+}
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+  #[tokio::test]
+  async fn handle_spawn() {
+    let handle = handle();
+    let join = handle.spawn(async { 5 });
+    assert_eq!(join.await.unwrap(), 5);
+  }
+
+  #[test]
+  fn handle_block_on() {
+    let handle = handle();
+    assert_eq!(handle.block_on(async { 0 }), 0);
+  }
 }

+ 3 - 0
core/tauri/src/error.rs

@@ -80,6 +80,9 @@ pub enum Error {
   /// user-provided URLs and paths.
   #[error("invalid url: {0}")]
   InvalidUrl(url::ParseError),
+  /// Task join error.
+  #[error(transparent)]
+  JoinError(Box<dyn std::error::Error + Send>),
 }
 
 impl From<serde_json::Error> for Error {

+ 2 - 2
core/tauri/src/updater/mod.rs

@@ -490,7 +490,7 @@ pub(crate) fn listener<R: Runtime>(
                   // emit {"status": "DONE"}
                   send_status_update(window.clone(), EVENT_STATUS_SUCCESS, None);
                 }
-              })
+              });
             });
           } else {
             send_status_update(window.clone(), EVENT_STATUS_UPTODATE, None);
@@ -500,7 +500,7 @@ pub(crate) fn listener<R: Runtime>(
           send_status_update(window.clone(), EVENT_STATUS_ERROR, Some(e.to_string()));
         }
       }
-    })
+    });
   });
 }