我制作了一个同时显示天气的LED时钟。我的程序循环执行几个不同的操作,每个操作有不同的间隔时间:
以下是我拥有的代码:
我发现代码没有按照我的预期工作 - 获取天气信息阻塞了循环的执行。我明白原因是
那么,在等待网络获取天气信息时,如何使LED继续更新呢?我发现我可以使用
- 每50毫秒更新LED,
- 每1秒检查光线水平(以调整亮度),
- 每10分钟获取天气,
- 还有一些其他的操作,但这些都不重要。
以下是我拥有的代码:
let mut measure_light_stream = tokio::time::interval(Duration::from_secs(1));
let mut update_weather_stream = tokio::time::interval(WEATHER_FETCH_INTERVAL);
let mut update_leds_stream = tokio::time::interval(UPDATE_LEDS_INTERVAL);
loop {
tokio::select! {
_ = measure_light_stream.tick() => {
let light = lm.get_light();
light_smooth.sp = light;
},
_ = update_weather_stream.tick() => {
let fetched_weather = weather_service.get(&config).await;
// Store the fetched weather for later access from the displaying function.
weather_clock.weather = fetched_weather.clone();
},
_ = update_leds_stream.tick() => {
// Some code here that actually sets the LEDs.
// This code accesses the weather_clock, the light level etc.
},
}
}
我发现代码没有按照我的预期工作 - 获取天气信息阻塞了循环的执行。我明白原因是
tokio::select!
的文档说,一旦表达式update_weather_stream.tick()
完成,其他分支就会被取消。那么,在等待网络获取天气信息时,如何使LED继续更新呢?我发现我可以使用
tokio::spawn
来启动一个单独的非阻塞“线程”来获取天气信息,但接下来我遇到了不可发送的weather_service
,更别提不能在线程之间共享的weather_clock
问题。我不想增加这些复杂性,我希望所有内容都在一个单一的线程中运行,就像select!
一样。
可重现的示例
use std::time::Duration;
use tokio::time::{interval, sleep};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut slow_stream = interval(Duration::from_secs(3));
let mut fast_stream = interval(Duration::from_millis(200));
// Note how access to this data is straightforward, I do not want
// this to get more complicated, e.g. care about threads and Send.
let mut val = 1;
loop {
tokio::select! {
_ = fast_stream.tick() => {
println!(".{}", val);
},
_ = slow_stream.tick() => {
println!("Starting slow operation...");
// The problem: During this await the dots are not printed.
sleep(Duration::from_secs(1)).await;
val += 1;
println!("...done");
},
}
}
}
weather_service
没有实现Send
,这使得任务变得更加困难(如果不使用类似spawn_local
的东西)。weather_clock
也不应该在任务之间共享。 - Shepmaster