美文网首页
Vapor学习之Async(异步)

Vapor学习之Async(异步)

作者: 小熊学编程 | 来源:发表于2019-08-07 17:04 被阅读0次

    Async 概览

    可能你已经注意到Vapor中的一些API会期望或返回一个通用的Future类型。对于第一次听说futures的人起初会存在些困惑但别担心,Vapor会让它们变得很易用。

    Promises 和 futures是有关联的,但也是截然不同的类型。Promises 用来创建futures。通常,你将使用Vapor的APIs所返回的future来工作从而不必担心创建promises。

    type description mutability methods
    Future 引用一个可能还是不可用的对象 只读 .map(to::) .flatMap(to::) do(:) catch(:)
    Promise 提供一些异步的对象 可读写 succeed(:) fail(:)

    Future是基于回调的异步API的替代品,Futures可以链式以及转化来使用,这是简单闭包无法企及的方式,这使得Futures非常强大。

    Transforming

    就像Swift中的可选项,futures可以被mapped以及flat-mapped。这些是你在futures上执行的最常见操作

    method signature description
    map to: U.Type, _: (T) -> U 把一个future Map成一个不同的值
    flatMap to: U.Type, _: (T) -> Future<U> 把一个future Map成一个不同的future值
    transform to: U 将一个future Map成一个已经可用值

    如果你查看Optional<T>Array <T>上的mapflatMap的方法签名,您将看到它们与Future <T>上的方法非常相似

    Map

    .map(to:_:)方法允许你将 future 的值转换成另一个值。一旦 Future 的数据准备就绪后,提供的闭包将会立即被调用。

    /// 设想我们从某个API获取了一个future string
    let futureString: Future<String> = ...
    
    /// 将这个 future string转为 integer
    let futureInt = futureString.map(to: Int.self) { string in
        print(string) // 实际的string
        return Int(string) ?? 0
    }
    
    /// 现在我们有了一个 future integer
    print(futureInt) // Future<Int>
    

    Flat Map

    .flatMap(to:_:) 方法允许你将 future 的值转换成另一个 future 值。因为 flatMap 可以使你避免生成内嵌的 futures(比如:Future<Future<T>>),所以以 "flat" 来命名。换言之,这将使得你的 更加扁平化。

    /// 假设我们从某个API获取一个 Future<String> 对象
    let futureString: Future<String> = ...
    
    /// 假设我们创建了一个http客户端
    let client: Client = ... 
    
    /// Flat-map 操作将Future<String>转为Future<Response>
    let futureResponse = futureString.flatMap(to: Response.self) { string in
        return client.get(string) // Future<Response>
    /// 现在我们就有了一个Future<Response>
    print(futureResponse) // Future<Response>
    

    提示: 上述例子中如果使用 .map(to:_:) 替代的话,我们将会最终得到 Future<Future<Response>>

    Transform

    .transform(_ :)方法允许你修改future's value,而忽略现有值。这对于转化那些对于future的实际价值并不重要的Future <Void>类型的结果特别有用。

    提示: Future<Void>,有时被称为信号,其唯一目的是通知你一些异步操作的完成或失败。

    /// 假设我们从某个API获取了一个 future<void>
    let userDidSave: Future<Void> = ...
    
    /// 将其转化为一个http状态
    let futureStatus = userDidSave.transform(to: HTTPStatus.ok)
    print(futureStatus) // Future<HTTPStatus>
    

    这个操作仍然是一个转变,即使我们已经提供了一个可使用的值用来transform.在之前的futures还未完成(或者失败)之前,这个future不会完成。

    Chaining

    Futures 有这么一个特性,转换可以被 chained 。它允许你可以很容易的进行多次转换。
    让我们修改上述示例:

    /// 假设我们获取一个future返回值从某个api
    let futureString: Future<String> = ...
    /// 假设我们已经初始化了一个客户端
    let client: Client = ... 
    
    /// 先转化为一个URL再转化为一个Response类型。
    let futureResponse = futureString.map(to: URL.self) { string in
        guard let url = URL(string: string) else {
            throw Abort(.badRequest, reason: "Invalid URL string: \(string)")
        }
        return url
    }.flatMap(to: Response.self) { url in
        return client.get(url)
    }
    print(futureResponse) //Future<Response>
    

    在初次调用 map 后,生成了个临时的 Future<URL>。然后该 future 立即被 flat-mapped 成 Future<Response>

    提示: 你可以在 map 和 flat-map 闭包中 throw 错误。这将引起 future 失败,并附带抛出来的错误信息。

    /// Assume we have a Request and some ViewRenderer
    let req: Request = ...
    let view: ViewRenderer = ...
    
    /// Render the view, using the Request as a worker. 
    /// This ensures the async work happens on the correct event loop.
    ///
    /// This assumes the signature is:
    /// func render(_: String, on: Worker)
    view.render("home.html", on: req)
    

    Futures

    因为 Future是异步执行的,我们必须使用闭包来进行交互并转换它们的值。就像 Swift 中的 optionals 一样, futures 可以被 mapped 和 flat-mapped 。

    Do / Catch

    与Swift的do / catch语法类似,futures也有一种do和catch方法来等待未来的结果。

    /// Assume we get a future string back from some API
    let futureString: Future<String> = ...
    futureString.do { string in
        print(string) // 实际值
    }.catch { error in
        print(error) // 一个Swift类型的错误
    }
    

    提示:.do.catch一起工作。如果你忘记.catch,编译器会警告你一个未使用的结果。不要忘记处理错误的情况!

    Always

    你可以使用always来添加一个无论是失败还是成功都会被调用的回调。

    /// Assume we get a future string back from some API
    let futureString: Future<String> = ...
    futureString.always {
        print("The future is complete!")
    }
    

    Wait

    你可以使用.wait()同步等待future完成。由于future可能会失败,所以这个调用会抛出异常。

     /// Assume we get a future string back from some API
    let futureString: Future<String> = ...
    /// 一直阻塞到string操作完成
    let string = try futureString.wait()
    print(string) /// String
    

    注意: 不要在一个vapor 的路由或者控制器中使用这个方法

    Promise

    大多数情况下,你是通过调用vapor的API's来获取一个需要操作的future。但是,在某些时候,你可能需要创建自己的promise。
    要创建promise,你需要访问EventLoop。Vapor中的所有容器都有一个可以使用的eventLoop属性。最常见的是,就是当前的Request

    /// Create a new promise for some string
    let promiseString = req.eventLoop.newPromise(String.self)
    print(promiseString) // Promise<String>
    print(promiseString.futureResult) // Future<String>
    /// Completes the associated future
    promiseString.succeed(result: "Hello")
    /// Fails the associated future
    promiseString.fail(error: ...)
    

    提示: 一个Promise对象只能够被完成一次,所有的后续完成操作都会被忽略。

    线程安全

    Promises可以从任何线程完成(succeed(result:) / fail(error:))。这就是为什么promise需要一个事件循环来初始化。Promise确保完成操作在它的事件循环执行。

    Event Loop (事件循环)

    它通常会为其运行的CPU中的每个内核创建一个事件循环在你的应用程序启动时。每个事件循环只有一个线程。如果你熟悉Node.js的事件循环,就会发现Vapor中的事件循环与Node.js中的非常相似。唯一的区别是Vapor可以在一个进程中运行多个事件循环,因为Swift支持多线程。
    每次客户端连接到你的服务器时,它都将被分配给其中一个事件循环。从这一点开始,服务器和该客户端之间的所有通信都将发生在同一个事件循环(并关联到这个事件循环的线程)上。
    事件循环负责跟踪每个连接的客户端的状态。如果有来自客户端的请求等待读取,则事件循环触发读取通知,导致数据被读取。一旦读取完整个请求,任何等待该请求数据的futures都将完成。

    Worker

    你也许会看到 Vapor 很多方法中都有一个 on: Worker 参数。这些方法通常是要执行异步任务,而且要访问 EventLoop

    Vapor 中常见的 Workers 有如下几个:
    - Application
    - Request
    - Response
    

    你可以在这些容器上使用.eventLoop属性来访问事件循环。

    print(app.eventLoop) // EventLoop
    

    Vapor中有许多方法需要传递当前worker。它通常会有像on: Worker这样的标签。如果你处于路由闭包或控制器中,请传递当前RequestResponse。如果你在启动应用程序时需要一个worker,请使用Application

    Blocking(阻塞)

    绝对关键的规则如下

    危险
    切勿直接在事件循环中进行阻塞调用。
    一个阻塞调用的例子,类似于libc.sleep(_ :).

    router.get("hello") { req in
        /// 使当前事件循环阻塞5s
        sleep(5)
    
        ///当线程重新工作室返回一个简单字符串
        return "Hello, world!"
    }
    

    sleep(_ :)是一个用于在提供的秒数内阻止当前线程的命令。如果你直接在事件循环中阻塞工作,则在阻塞工作期间,事件循环将无法响应分配给它的任何其他客户端。换句话说,如果你在事件循环中进行睡眠(5),则连接到该事件循环的所有其他客户端(可能为数百或数千个)将被延迟至少5秒
    确保只有在后台才运行任何阻塞工作。当这项工作以非阻塞方式完成时,使用Promise通知事件循环。

    router.get("hello") { req in
        /// 创建一个空的promise
        let promise = req.eventLoop.newPromise(Void.self)
    
        /// 在后台线程执行
        DispatchQueue.global() {
            /// 使后台线程睡眠
            /// 这不会影响到当前的事件循环
            sleep(5)
    
            /// 当阻塞工作完成时
            /// promise和他关联的future会被完成
            promise.succeed()
        }
    
        /// 等待 future完成
        /// 将结果转为一个简单的字符
        return promise.futureResult.transform(to: "Hello, world!")
    
    }
    

    并非所有的阻塞调用会像sleep(:)如此明显,当你怀疑你调用的方法可能被阻塞时,研究下这个方法本身或者询问相关的人。如果该函数正在执行磁盘或者网络IO并且使用的是一个同步API(无回调或者futures),那么就会发生阻塞的情况。

    提示: 如果阻塞工作是应用程序的核心部分,则应该考虑使用BlockingIOThreadPool来控制你创建的阻塞工作的线程数。当阻塞工作正在被完成时,这将避免你的事件循环长期获取不到 CPU 时间片。。

    相关文章

      网友评论

          本文标题:Vapor学习之Async(异步)

          本文链接:https://www.haomeiwen.com/subject/oxnodctx.html