「这是我参与11月更文挑战的第 17 天,活动详情查看:2021最后一次更文挑战」
这个函数被标记为 #[tokio::main]
,意味着它将被执行在异步运行时中。
我们现在使用的所有函数都可以被标记为异步,这意味着我们可以等待结果。在引擎盖,Rust将这些函数转换为名为 Futures 的结构,在之前的迭代中,我们手动构建了这些结构。Futures可以是Ready,也可以是Waiting。
你可以在一个Futures上调用 poll()
,它只是询问。"你是准备好了,还是在等待?" 如果future 准备好了,它就会把响应传递上去,如果它在等待,它就会只用Pending来响应。它可以返回一个 wake() 回调,让调用者知道有一个值。当 wake()
被执行时,调用者将知道再次 poll()
这个 future,将其解析为一个值。
这一切都涉及到很多的流程。这个 async/await
为我们抽象了这些概念。我们只是像平常一样写函数,只是把它们标记为async,当控制流到达一个需要等待结果解析的点时,我们可以使用await。如果这些是在 #[tokio::main]
这样的执行器中使用,运行时将包揽所有的细节。
这些 await
的点不会阻断控制流,而是会切出去,让位于这个执行器上的其他运行线程,直到底层的 future
从 poll()
请求中返回为 Ready<T>
。这使得我们的代码更容易编写。
这个顶层函数读取我们的参数结构以建立要绑定的 SocketAddr
,用 tracing_subscriber
启动日志系统,并建立我们的状态管理。然后,我们调用 serve()
异步函数并等待结果。
这个执行器将永远运行下去,直到用户杀死这个进程,并且可以为我们无缝地处理多个并发连接。
为了提前了解函数情况,这是serve()的签名:
async fn serve<C, H, F>(
addr: std::net::SocketAddr,
context: Arc<C>,
handler: H,
) -> hyper::Result<()>
where
C: 'static + Send + Sync,
H: 'static + Fn(Request) -> F + Send + Sync,
F: Future<Output = Response> + Send,
{
// ...
}
它要求我们传递一个 addr,然后是一个 Arc<C>
。
这个C类型是我们的应用程序的状态,为了使其发挥作用,它必须实现 Send+Sync trait
。Send
意味着该值可以被发送到另一个线程,而 Sync
意味着它可以在线程之间同时共享。
在我们的案例中,我们不需要太过考虑这个问题。我们使用一个 Arc<RwLock<T>>
来允许可变性,以提供一个可以从多个线程访问并根据需要安全可变的类型。只要每个线程都能持有对应的锁,我们就不需要担心并发的线程会互相竞争。每次只有一个线程能够写到这个类型,所以每个新的读者都会有正确的数据。
最后,我们需要添加一个H类型的处理程序。这些类型开始剥离一些async为我们所做的事情。剥去Send+Sync trait
的约束,这个函数满足了 Fn(Request) -> Future<Output = Response>
的trait约束。
因为我们在一个异步环境中,我们可以直接写异步 fn handle(request: Request) -> Response
→ 这是一个从请求到响应的异步函数。使用Rust的 async/await
,我们可以简单地写出我们的想要的代码。
我们很快就会回到处理程序上。首先,我们有一点设置需要处理。