有没有办法强制执行类似于GCD序列队列的异步/等待调用的序列调度? [英] Is there a way to enforce serial scheduling of async/await calls similar to a GCD serial queue?

查看:0
本文介绍了有没有办法强制执行类似于GCD序列队列的异步/等待调用的序列调度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

稍微简化一下我的用例,我有一系列的异步任务,我想从调用站点发出它们,并在它们完成时得到回调,但根据设计,我一次只想执行一个任务(每个任务都依赖于前一个任务的完成)。

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

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

我曾深入研究过使用Actor,但就我所知,没有办法真正使用该方法强制/预期执行序列。

--

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

添加以下示例代码:

// 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。如果您想了解包装网络请求的Operation子类是什么样子,请参阅https://stackoverflow.com/a/57247869/1271826。但这是没有意义的,因为您现在应该只使用async-await

  2. 您说:

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

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

    但是,如果您的方法真的是异步的,那么,参与者将不能确保顺序执行。演员是为重入而设计的。请参阅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. 也许这是无关的,但如果您引入的是异步等待,我建议您不要使用withCheckedContinuation。显然,如果iOS 15(或MacOS 12)和更高版本,我会使用新的异步URLSession方法。例如,如果您需要返回到iOS 13,我会使用withTaskCancellationHandlerwithThrowingCheckedContinuation。请参阅https://stackoverflow.com/a/70416311/1271826

这篇关于有没有办法强制执行类似于GCD序列队列的异步/等待调用的序列调度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆