swift 如何停止正在进行的调度组执行并启动新的调度组?

vd2z7a6w  于 11个月前  发布在  Swift
关注(0)|答案(2)|浏览(115)

所以我有一个分页的API,每次用户进入屏幕时我都要调用它。下面的代码工作正常,但有一个问题,如果说API调用正在进行中,用户后退并再次出现,那么两个执行同时开始。并且必须等待两个执行完成。所以,我想要的是停止正在进行的API调用,并开始新的请求时,新的一个如果此设置是错误的,以任何方式请通知我,以及我做错了什么?

func storeAllData(matchID: Int64) {
        GlobalVariable.isFetchingData = true
        dbManager = DbManager.shared
        if let lastScore = dbManager.getLastBallForCurrentMatch(matchID: matchID) {
            self.fetchAllScore(scoreID: lastScore.id ?? 0, matchID: matchID)
        } else {
            self.fetchAllScore(scoreID: 0, matchID: matchID)
        }
    }
    
    func fetchAllScore(scoreID: Int = 0, matchID: Int64) {
        let id = matchID
        backgroundQueue.async { [self] in
            autoreleasepool {
                fetchPaginatedData(matchID:Int(id), scoreID: scoreID, page: 1)
                dispatchGroup.wait()
                print("All API calls finished")
            }
        }
    }
    
    func fetchPaginatedData(matchID: Int,scoreID: Int = 0, page: Int) {
        dispatchGroup.enter()
        MatchesNetworkManager.sharedInstance.getAllScore(matchID: Int64(matchID), scoreID: Int64(scoreID), page: Int64(page), completionHandler: { [weak self] (allSocre, isSuccess, errorStr) in
            
            if isSuccess {
                if allSocre?.match?.scores != nil {
                    self?.oldScores.append(contentsOf: ((allSocre?.match?.scores)!))
                    self?.backgroundQueue.async {
                        if let scores = allSocre?.match?.scores as? [Scores] {
                            self?.dbManager.insertRecords(matchID: matchID, records: scores)
                            DispatchQueue.main.async {
                                self?.oldScores = []
                                if allSocre?.page_num ?? 0 == 1 {
                                    self?.updateScoreView()
                                }
                                if (allSocre?.page_num ?? 0) < (allSocre?.total_pages ?? 0) {
                                    self?.fetchPaginatedData(matchID: matchID,page: (allSocre?.page_num ?? 1) + 1)
                                } else {
                                    // All pages have been fetched
                                    GlobalVariable.isFetchingData = false
                                    print("All API calls finished")
                                    NotificationCenter.default.post(name: .refreshMarketView, object: nil, userInfo: nil)
                                    self?.dispatchGroup.leave()
                                }
                            }
                        }
                    }
                } else {
                    self?.updateScoreView()
                    GlobalVariable.isFetchingData = false
                }
                
            } else {
                print("API call error: \(errorStr)")
                self?.updateScoreView()
                GlobalVariable.isFetchingData = false
                self?.dispatchGroup.leave()
            }
        })
    }

字符串

nimxete2

nimxete21#

在这些(以及类似的)平台上,大多数并发抽象的现实是,一旦启动,工作单元几乎完全无法被强制取消(除了终止进程)。
通常处理这种情况的方法是检查工作单元本身 * 内部 * 的标志,指示它们应该被取消,此时它们可以进行任何需要的清理(如果有的话),并提前返回。如果您有一个调度组,您可以将标志与该组关联,然后在每个操作开始时(或在操作内循环的每次迭代的顶部)显式地检查标志,并且如果组被取消则提前返回。
你可以临时这么做。你可以使用现有的抽象(比如NSOperation 's cancel method),但即使这样,你也会注意到 Discussion 部分的第一行说:“This method does not force your operation code to stop.”你的操作代码仍然必须检查标志。
例如,您可以使用NSBlockOperation,创建操作示例,然后捕获对执行真实的工作的块闭包内的NSBlockOperation示例的零弱引用,然后使用该引用检查NSBlockOperation是否已被取消(或释放)。
长话短说,这里没有免费的午餐。

i5desfxk

i5desfxk2#

从本质上讲,您似乎在寻找可取消的异步任务。(派遣组,使用wait阻塞线程直到请求完成)。我猜理论上可以通过为URLSession请求保存URLSessionTask对象来完成(我假设它们被埋在你的“网络管理器”中)和cancel那些当有问题的视图消失时。但那是不优雅的。
回到过去,建议是将异步任务 Package 在Operation子类中,尽量避免使用调度组,执行适当的isFinishedisExecuting KVO通知。您的Operation子类将覆盖cancel以取消网络请求。参见https://stackoverflow.com/a/40560463/1271826示例。无论如何,如果实现这类Operation子类来 Package 异步工作,则取消工作相当简单。
但是,在建议了如何使用遗留API实现这一点之后,(即操作队列),我必须承认,我不会考虑第二,现在。相反,我们会达到Swift并发。这不仅管理异步任务的依赖关系,但是标准URLSession方法data(from:)data(for:)本身支持消除。(这是假设你甚至使用URLSession;你没有和我们分享相关的代码。)你最终会得到更简单的代码,处理异步依赖关系,并支持取消。但这将需要更彻底的重写代码,你没有分享,所以我们甚至不能开始向你提供具体的建议。但是请参阅WWDC视频Meet async/await in SwiftSwift concurrency: Update a sample app(以及其中相应的“相关视频”部分中链接的其他视频)。
在下面的评论中,你注意到你正在使用Alamofire。它使用URLSession并支持Swift并发。
例如,考虑Alamofire的这种传统方法:

class LegacyNetworkManager {
    static let shared = LegacyNetworkManager()

    private init() { }

    func fetch(completion: @escaping (Result<[Post], AFError>) -> Void) {
        AF.request("https://jsonplaceholder.typicode.com/posts")
            .response(responseSerializer: .decodable(of: [Post].self)) { response in
                completion(response.result)
            }
    }
}

字符串
相反,你可以使用Alamofire采用Swift并发,现在你有了一个可取消的API:

actor NetworkManager {
    static let shared = NetworkManager()

    private init() { }

    func posts() async throws -> [Post] {
        try await AF.request("https://jsonplaceholder.typicode.com/posts")
            .serializingDecodable([Post].self)
            .value
    }
}


使用此可取消的格式副本,您可以执行以下操作:

import os.log

private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SwiftConcurrencyViewController")

class SwiftConcurrencyViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    private let networkManager = NetworkManager.shared
    private let databaseManager = DatabaseManager.shared

    private var task: Task<Void, Error>?

    var posts: [Post] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UINib(nibName: "PostCell", bundle: nil), forCellReuseIdentifier: "PostCell")
    }
    
    // start request when view appears …

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        logger.debug("launching task")
        task = Task { await fetch() }
    }

    // … but if not finished by the time the view disappears, cancel it

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        logger.debug("canceling (if not already done)")
        task?.cancel()
    }
}

extension SwiftConcurrencyViewController: UITableViewDataSource {
    …
}

private extension SwiftConcurrencyViewController {
    func fetch() async {
        do {
            logger.debug("fetching")
            posts = try await networkManager.posts()
            logger.debug("fetched")
            tableView.reloadData()
            try await databaseManager.save(posts)
        } catch {
            logger.error("\(error)")
            show(error.localizedDescription)
        }
    }
}


请参阅https://github.com/robertmryan/AlamofireWithSwiftConcurrency,了解Minimal, Reproducible Example对比GCD和Swift并发方法。
现在,我认识到这与您的示例有很大的不同(因为您没有充分分享您的实现),所以不要太迷失在我的示例中,但请注意:

  • 即使在GCD示例中,我们也不会使用分派组;但是
  • 通过采用Swift并发,我们不仅可以管理异步任务之间的依赖关系,还可以享受取消逻辑。

相关问题