【问题标题】:Is there a way to enforce serial scheduling of async/await calls similar to a GCD serial queue? [duplicate]有没有办法强制执行类似于 GCD 串行队列的异步/等待调用的串行调度? [复制]
【发布时间】:2022-01-14 05:26:26
【问题描述】:

使用 Swift 的新 async/await 功能,我想模拟串行队列的调度行为(类似于过去使用 DispatchQueueOperationQueue 的方式)。

稍微简化一下我的用例,我有一系列异步任务,我想从调用站点触发并在它们完成时获得回调,但根据设计我想一次只执行一个任务(每个任务取决于上一个任务的完成情况)。

今天,这是通过将Operations 与maxConcurrentOperationCount = 1 放在OperationQueue 上来实现的,并在适当的时候使用Operation 的依赖功能。我已经使用 await withCheckedContinuation 围绕现有的基于闭包的入口点构建了一个 async/await 包装器,但我正试图弄清楚如何将整个方法迁移到新系统。

这可能吗?这是否有意义,还是我从根本上违背了新的 async/await 并发系统的意图?

我已经研究了一些使用 Actors 的方法,但据我所知,没有办法真正强制/期望使用这种方法进行串行执行。

--

更多上下文 - 这包含在网络库中,今天的每个操作都是针对新请求的。 Operation 进行一些请求预处理(考虑身份验证/令牌刷新,如果适用),然后触发请求并继续下一个 Operation,从而避免在不需要时重复身份验证预处理。从技术上讲,每个 Operation 并不知道它依赖于先前的操作,但 OperationQueue 的调度强制执行串行执行。

在下面添加示例代码:

// Old entry point
func execute(request: CustomRequestType, completion: ((Result<CustomResponseType, Error>) -> Void)? = nil) {
    let operation = BlockOperation() {
        // do preprocessing and ultimately generate a URLRequest
        // We have a URLSession instance reference in this context called session
        let dataTask = session.dataTask(with: urlRequest) { data, urlResponse, error in
        completion?(/* Call to a function which processes the response and creates the Result type */)
        dataTask.resume()
    }

    // queue is an OperationQueue with maxConcurrentOperationCount = 1 defined elsewhere
    queue.addOperation(operation)
}

// New entry point which currently just wraps the old entry point
func execute(request: CustomRequestType) async -> Result<CustomResponseType, Error> {
        await withCheckedContinuation { continuation in
            execute(request: request) { (result: Result<CustomResponseType, Error>) in
                continuation.resume(returning: result)
            }
        }
    }

【问题讨论】:

  • “我想从呼叫站点启动并在他们完成时获得回调”不。没有回调。这才是重点。从你的想法中抹去这个概念。演员就是一个上下文序列化器,所以请展示你的代码不能完成这个。一旦你说await,在异步材料完成之前你无法继续,那么问题是什么?在没有回调的情况下理顺这些东西,这正是 async/await 所做的。
  • 回调是一个糟糕的词选择,我已经编写的包装器是一个异步函数,它返回一个结果(替换旧的闭包/回调入口点)——我只是想使实现现代化里面。
  • 正如我所说,您应该提供一些代码。你要求做的事情听起来很简单,所以看看为什么不是这样会很有用。例如,我轻松编写了一个绘制 Mandelbrot 集的演示示例,在现有重绘完成之前,您甚至无法开始重绘该集;多亏了一位演员,他们完全按照您的建议顺序排列。所以请说明为什么这对你不起作用。
  • 是的,只要演员中没有代码说等待。一旦它这么说,演员就变成了可重入的。
  • 如果这是问题所在,我会说这是 stackoverflow.com/questions/68686601/… 的副本。我只是从那里重复我的答案。

标签: swift async-await grand-central-dispatch nsoperationqueue


【解决方案1】:

一些观察:

  1. 为了清楚起见,您的操作队列实现“[强制]串行执行”网络请求。您的操作只是包装这些请求的准备,而不是这些请求的性能(即操作立即完成,并且不等待请求完成)。因此,例如,如果您的身份验证是一个网络请求,而第二个请求需要在继续之前完成,那么这种BlockOperation 类型的实现不是正确的解决方案。

    通常,如果使用操作队列来管理网络请求,您会将整个网络请求和响应包装在自定义的异步Operation 子类(而不是BlockOperation)中,此时您可以使用操作队列依赖项和/或maxConcurrentOperationCount。如果您想了解包装网络请求的Operation 子类是什么样的,请参阅https://stackoverflow.com/a/57247869/1271826。但这没有实际意义,因为你现在应该只使用async-await

  2. 你说:

    我基本上可以完全跳过队列并将每个Operation 替换为actor 上的异步方法来完成相同的事情?

    没有。 Actor 可以确保按顺序执行 同步 方法(那些没有 await 调用的方法,在这些情况下,您不希望方法本身带有 async 限定符)。

    但是,如果您的方法是真正异步的,那么,不,actor 将无法确保顺序执行。 Actor 是为可重入而设计的。见SE-0306 - Actors » Actor reentrancy

  3. 如果您希望后续网络请求等待认证请求完成,您可以保存认证请求的Task。然后后续请求可以await那个任务:

    actor NetworkManager {
        let session: URLSession = ...
    
        var loginTask: Task<Bool, Error>?
    
        func login() async throws -> Bool {
            loginTask = Task { () -> Bool in
                let _ = try await loginNetworkRequest()
                return true
            }
            return try await loginTask!.value
        }
    
        func someOtherRequest(with value: String) async throws -> Foo {
            let isLoggedIn = try await loginTask?.value ?? false
            guard isLoggedIn else {
                throw URLError(.userAuthenticationRequired)
            }
            return try await foo(for: createRequest(with: value))
        }
    }
    
  4. 也许这无关紧要,但如果你要引入 async-await,我建议不要使用 withCheckedContinuation。显然,如果 iOS 15(或 macOS 12)及更高版本,我会使用新的 async URLSession 方法。例如,如果您需要返回 iOS 13,我会使用 withTaskCancellationHandlerwithThrowingCheckedContinuation。见https://stackoverflow.com/a/70416311/1271826

【讨论】:

    猜你喜欢
    • 2013-03-15
    • 2021-10-24
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-23
    • 1970-01-01
    相关资源
    最近更新 更多