Home > Software design >  How can I quit/pause/unpause a timer thread
How can I quit/pause/unpause a timer thread

Time:10-26

I am trying to build a pausable pomodoro timer, but can't figure out how my threads should be set up. Specifically I am having a hard time on finding a way to pause/quit the timer thread. Here's what I am currently trying to do:

I have an input thread collecting crossterm input events and sending them via an mpsc-channel to my main thread

My main thread runs the timer (via thread::sleep) and receives the input messages from the other thread

However because sleep is a blocking operation, the main thread is often unable to react to my key-events.

Any advice how I could be going about this?

Here is a minimum reproducable example:

pub fn run() {
    enable_raw_mode().expect("Can run in raw mode");

    let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
        mpsc::channel();

    poll_input_thread(input_worker_tx);
    main_loop(input_worker_rx);
}

fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
    let stdout = std::io::stdout();
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).expect("Terminal could be created");
    terminal.clear().expect("Terminal could be cleared");

    let mut remaining_time = 20;

    while remaining_time > 0 {
        if let InputEvent::Quit = handle_input(&input_worker_rx) {
            break;
        };

        remaining_time -= 1;
        let time = util::seconds_to_time(remaining_time);
        view::render(&mut terminal, &time);

        sleep(time::Duration::new(1, 0));
    }

    quit(&mut terminal, Some("Cya!"));
}



pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
    let tick_rate = Duration::from_millis(200);

    thread::spawn(move || {
        let mut last_tick = Instant::now();
        loop {
            let timeout = tick_rate
                .checked_sub(last_tick.elapsed())
                .unwrap_or_else(|| Duration::from_secs(0));

            if event::poll(timeout).expect("poll works") {
                let crossterm_event = event::read().expect("can read events");
                input_worker_tx
                    .send(CliEvent::Input(crossterm_event))
                    .expect("can send events");
            }

            if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
                last_tick = Instant::now();
            }
        }
    });
}

pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>) -> InputEvent {
    match input_worker_rx.try_recv() {
        Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
            KeyEvent {
                code: KeyCode::Char('q'),
                ..
            }
            | KeyEvent {
                code: KeyCode::Char('c'),
                modifiers: KeyModifiers::CONTROL,
                ..
            } => {
                return InputEvent::Quit;
            }

            _ => {}
        },
        Ok(CliEvent::Tick) => {}
        Err(TryRecvError::Empty) => {}
        Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
        _ => {}
    }

    InputEvent::Continue
}

CodePudding user response:

Don't use sleep and don't use try_recv. Instead use recv_timeout with a timeout corresponding to your sleep duration:

pub fn run() {
    enable_raw_mode().expect("Can run in raw mode");

    let (input_worker_tx, input_worker_rx): (Sender<CliEvent<Event>>, Receiver<CliEvent<Event>>) =
        mpsc::channel();

    poll_input_thread(input_worker_tx);
    main_loop(input_worker_rx);
}

fn main_loop(input_worker_rx: Receiver<CliEvent<Event>>) {
    let stdout = std::io::stdout();
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).expect("Terminal could be created");
    terminal.clear().expect("Terminal could be cleared");

    let target_time = Instant::now()   Duration::from_secs(20);

    while target_time > Instant::now() {
        if let InputEvent::Quit = handle_input(&input_worker_rx, Duration::from_secs (1)) {
            break;
        };

        let time = (target_time - Instant::now()).as_secs();
        view::render(&mut terminal, &time);
    }

    quit(&mut terminal, Some("Cya!"));
}



pub fn poll_input_thread(input_worker_tx: Sender<CliEvent<Event>>) {
    let tick_rate = Duration::from_millis(200);

    thread::spawn(move || {
        let mut last_tick = Instant::now();
        loop {
            let timeout = tick_rate
                .checked_sub(last_tick.elapsed())
                .unwrap_or_else(|| Duration::from_secs(0));

            if event::poll(timeout).expect("poll works") {
                let crossterm_event = event::read().expect("can read events");
                input_worker_tx
                    .send(CliEvent::Input(crossterm_event))
                    .expect("can send events");
            }

            if last_tick.elapsed() >= tick_rate && input_worker_tx.send(CliEvent::Tick).is_ok() {
                last_tick = Instant::now();
            }
        }
    });
}

pub fn handle_input(input_worker_rx: &Receiver<CliEvent<Event>>, timeout: Duration) -> InputEvent {
    match input_worker_rx.recv_timeout(timeout) {
        Ok(CliEvent::Input(Event::Key(key_event))) => match key_event {
            KeyEvent {
                code: KeyCode::Char('q'),
                ..
            }
            | KeyEvent {
                code: KeyCode::Char('c'),
                modifiers: KeyModifiers::CONTROL,
                ..
            } => {
                return InputEvent::Quit;
            }

            _ => {}
        },
        Ok(CliEvent::Tick) => {}
        Err(TryRecvError::Empty) => {}
        Err(TryRecvError::Disconnected) => eject("Input handler disconnected"),
        _ => {}
    }

    InputEvent::Continue
}
  • Related