Hi, for below example code:
use std::io::Result as IoResult;
use tokio::task::{AbortHandle, JoinSet};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use crossterm::event::{
DisableFocusChange, DisableMouseCapture,
EnableFocusChange, EnableMouseCapture, Event,
EventStream, KeyCode, KeyEventKind,
KeyEventState, KeyModifiers,
};
type TaskResult = IoResult<()>;
struct EventLoop {
// This actually doesn't have any meaning.
// Just want to show that this event loop may
// contains many data to share across different tasks/threads
pub some_data: Arc<RwLock<HashMap<i32, i32>>>,
// All spawned tasks should be managed by this,
// so I could easily detach/join/abort all of them.
pub tasks: JoinSet<TaskResult>,
}
#[derive(Copy, Clone)]
struct Context {
// Some shared global data
pub some_data: Arc<RwLock<HashMap<i32, i32>>>,
// Question-1: How to share JoinSet here?
pub tasks: &'static mut JoinSet<TaskResult>,
}
async fn job1(ctx: Context) -> TaskResult {
// Update the shared global data by first lock it
ctx.some_data.write().unwrap().insert(1, 1);
Ok(())
}
async fn job2(ctx: Context) -> TaskResult {
// Question-2: How to spawn `job1` inside this `job2` ?
// since I need to use the JoinSet `tasks` to spawn `job2` here.
ctx.tasks.spawn(async move || { job1(ctx).await? });
}
impl EventLoop {
pub async fn new() -> IoResult<Self> {
EventLoop {
tasks: JoinSet::new(),
}
}
pub fn init(&mut self) -> IoResult<()> {
let ctx = Context {
some_data: self.some_data.clone(),
// Question-3: The `'static` lifetime cannot compile,
// Because it's not static lifetime.
tasks: &'static mut self.tasks
};
self.tasks.spawn(async move || {job2(ctx).await? );
}
pub fn run(&mut self) -> IoResult<()> {
let mut reader = EventStream::new();
unsafe {
// Fix multiple mutable references.
let mut raw_self = NonNull::new(self as *mut EventLoop)
.unwrap();
loop {
tokio::select! {
// Receive terminal keyboard/mouse events
polled_event = reader.next() => match polled_event {
// If received some special keys, exit the loop
break;
}
// Join the completed tasks
Some(joined_task) = raw_self.as_mut().tasks.join_next() => match joined_task {
Ok(task_result) => {/* Skip */}
Err(e) => println!("{}", e),
}
}
}
}
}
}
#[tokio::main]
async fn main() -> IoResult<()> {
let mut evloop = EventLoop::new();
evloop.init().await?;
evloop.run().await?;
}
As mentioned in above sample code comments, I want to use JoinSet
along with crossterm EventStream
, so I could both receives terminal keyboard/mouse events and spawn async tasks in background.
But to let tasks update the data inside global context, or even spawn new tasks inside the task itself, I want to share the JoinSet
across different tasks.
How could I do it?
I read the tokio tutorial: Shared state | Tokio - An asynchronous Rust runtime, it recommends two strategy about shared state:
- Share state with mutex, which is exactly what I'm doing in this sample code.
- Spawn a task to manage the state and use message passing to operate on it.
I'm confused if I should choose the 2nd strategy, will that solve my issue here?