Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

异步编程

异步编程是一种并发编程风格,其中程序可以在等待操作(如 I/O)完成时执行其他工作。这与同步编程不同,在同步编程中,程序会等待每个操作完成后再继续下一个。

X 语言内置了对异步编程的支持,使用 asyncwait 关键字。在本章中,我们将探讨如何使用这些功能编写高效的异步代码。

什么是异步编程?

在同步编程中,当你调用一个执行 I/O 的函数(如读取文件或进行网络请求)时,程序会等待(阻塞)直到该操作完成。在等待期间,程序除了等待之外什么都不做。

在异步编程中,当你调用一个异步函数时,它会立即返回一个“未来“或“承诺“对象,而不是等待操作完成。然后,你可以继续做其他工作,当操作最终完成时,你可以“等待“未来以获取结果。

这对于具有大量 I/O 的程序特别有用,如 Web 服务器、数据库客户端或任何需要同时处理多个连接的东西。

Async 和 Wait 关键字

X 语言使用两个关键字进行异步编程:

  • async - 声明一个函数是异步的
  • wait - 等待异步操作完成

让我们看一个简单的例子:

async function fetch_data(url: String) needs Async -> Result<String, String> {
  // 模拟异步网络请求
  wait thread::sleep(time::Duration::from_seconds(2))
  Ok(String::from("来自 ") + url + " 的数据")
}

async function main() needs Async, IO {
  println("开始获取数据...")
  let result = wait fetch_data(String::from("https://example.com"))
  when result is {
    Ok(data) => println("得到: {}", data),
    Err(e) => eprintln("错误: {}", e)
  }
}

让我们分解一下:

  1. async function - 声明 fetch_data 是一个异步函数。异步函数总是返回一个 future。
  2. wait - 在 fetch_data 调用上暂停执行,直到 future 完成。
  3. needs Async - 声明这些函数具有异步效果。

运行时多个异步操作

异步编程的真正威力来自于同时运行多个异步操作。让我们看看如何做到这一点:

async function fetch_url(url: String) needs Async -> String {
  wait thread::sleep(time::Duration::from_seconds(1))
  String::from("来自 ") + url + " 的数据"
}

async function main() needs Async, IO {
  println("开始获取...")

  // 开始两个 fetch 操作——它们同时运行
  let future1 = fetch_url(String::from("https://example.com"))
  let future2 = fetch_url(String::from("https://rust-lang.org"))

  // 等待两者都完成
  let result1 = wait future1
  let result2 = wait future2

  println("结果 1: {}", result1)
  println("结果 2: {}", result2)
}

在这个例子中,两个 fetch 操作同时运行,所以整个程序大约需要 1 秒,而不是 2 秒(如果我们按顺序等待它们的话)。

Join 和 Select

X 语言提供了组合多个 future 的实用程序:

Join

join 等待所有 future 完成并返回它们的结果:

async function main() needs Async, IO {
  let (result1, result2) = wait join(
    fetch_url(String::from("https://example.com")),
    fetch_url(String::from("https://rust-lang.org"))
  )

  println("两者都完成了!")
}

Select

select 等待任何一个 future 完成并返回第一个结果:

async function main() needs Async, IO {
  let result = wait select(
    fetch_url(String::from("https://example.com")),
    fetch_url(String::from("https://rust-lang.org"))
  )

  println("第一个完成的: {}", result)
}

这对于实现超时很有用:

async function fetch_with_timeout(url: String, timeout: Duration) needs Async -> Result<String, String> {
  wait select(
    fetch_url(url).then(function(r) { Ok(r) }),
    (async function() {
      wait thread::sleep(timeout)
      Err(String::from("超时"))
    })()
  )
}

异步 I/O

异步编程对于 I/O 绑定操作最有用。X 语言的标准库提供了许多异步版本的常用 I/O 操作:

async function read_file_async(path: String) needs Async, FileIO -> Result<String, String> {
  // 异步读取文件——不会阻塞
  fs::read_to_string_async(path)
}

async function write_file_async(path: String, contents: String) needs Async, FileIO -> Result<(), String> {
  // 异步写入文件——不会阻塞
  fs::write_async(path, contents)
}

异步 Trait

你也可以定义具有异步方法的 trait:

trait Database {
  async function connect(url: String) needs Async -> Result<Self, String>
  async function query(self: &Self, sql: String) needs Async -> Result<List<Row>, String>
}

错误处理

异步函数中的错误处理与同步函数中的工作方式相同——你使用 Result

async function fetch_with_retry(url: String, max_retries: integer) needs Async -> Result<String, String> {
  let mut attempt = 0
  while attempt < max_retries {
    when wait fetch_url(url) is {
      Ok(result) => return Ok(result),
      Err(e) => {
        attempt = attempt + 1
        if attempt == max_retries {
          return Err(e)
        }
        wait thread::sleep(time::Duration::from_seconds(1))
      }
    }
  }
  Err(String::from("意外错误"))
}

实际例子:Web 服务器

让我们看一个更实际的例子——一个简单的异步 Web 服务器:

type Request = {
  method: String,
  path: String,
  body: String
}

type Response = {
  status: integer,
  body: String
}

async function handle_request(req: Request) needs Async -> Response {
  // 模拟一些异步工作,如数据库查询
  wait thread::sleep(time::Duration::from_millis(10))

  when req.path is {
    "/" => {
      { status: 200, body: String::from("你好,世界!") }
    },
    "/about" => {
      { status: 200, body: String::from("关于我们") }
    },
    _ => {
      { status: 404, body: String::from("未找到") }
    }
  }
}

async function main() needs Async, IO, Net {
  println("服务器在 :8080 上启动...")

  let listener = net::TcpListener::bind("127.0.0.1:8080")?

  // 异步接受连接
  while let Some(stream) = wait listener.accept() {
    // 生成新任务来处理每个连接
    spawn async {
      let request = wait read_request(stream)?
      let response = wait handle_request(request)
      wait send_response(stream, response)?
    }
  }
}

这个服务器可以同时处理多个连接,因为每个连接都在自己的异步任务中处理。

最佳实践

关于异步编程的一些最佳实践:

  1. 不要在异步代码中阻塞:避免在异步函数中进行阻塞操作——它们会阻塞整个执行器。如果你需要做阻塞的事情,将其生成到单独的线程中。

  2. 使用适当的抽象:在可用时使用 joinselect 和其他 future 组合器。

  3. 限制并发性:同时运行太多任务会导致问题——使用信号量或其他机制来限制并发性。

  4. 处理取消:异步任务可以被取消——确保你的代码通过清理资源来正确处理这个问题。

  5. 测试异步代码:测试异步代码可能很棘手——使用专门为异步测试设计的测试工具。

总结

X 语言中的异步编程:

  • 使用 async 声明异步函数
  • 使用 wait 等待异步操作
  • 允许同时运行多个操作
  • 对 I/O 绑定工作最有用
  • 使用 join 等待所有操作
  • 使用 select 等待第一个操作
  • Result 集成用于错误处理

异步编程是构建高效、可扩展应用程序的强大工具!

在下一章中,我们将探讨元编程!