是否有类似于GCD串行队列的方法来强制异步/等待调用的串行调度



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

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

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

这可能吗?这是否有意义,或者我是否从根本上违背了新的异步/等待并发系统的意图?

我已经深入研究了Actor的使用,但据我所知,用这种方法无法真正强制/期望串行执行。

--

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

一些观察结果:

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

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

  2. 你说:

    我基本上可以完全跳过队列,用参与者上的异步方法替换每个Operation来完成同样的事情?

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

    但是,如果您的方法真的是异步的,那么,不,actor将无法确保顺序执行。演员是为重新进入而设计的。参见SE-0306-演员»演员重新进入。

  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. 如果您正在寻找一般的类似队列的行为,可以考虑AsyncChannel。例如,在https://stackoverflow.com/a/75730483/1271826,我为URL创建了一个AsyncChannel,编写了一个循环来遍历该通道,并为每个通道执行下载。然后,当我想开始新的下载时,我send频道的一个新URL。

  5. 也许这是无关的,但如果您正在引入异步等待,我建议不要使用withCheckedContinuation。显然,如果iOS 15(或macOS 12(及更高版本,我会使用新的异步URLSession方法。例如,如果您需要返回iOS13,我会使用withTaskCancellationHandlerwithThrowingCheckedContinuation。看见https://stackoverflow.com/a/70416311/1271826.

相关内容

  • 没有找到相关文章

最新更新