使用Alamofire实现Web3 API调用时链切换崩溃的并发问题解决
问题解决:切换链后Web3并发调用崩溃及异步请求终止方案
核心问题分析
你当前的代码存在几个关键问题导致崩溃:
- Task Group的
cancelAll()仅标记任务为取消状态,但Web3Wrapper的processValue方法未响应任务取消,导致已取消的请求仍在执行,切换链后旧请求的链ID/RPC URL与当前环境不匹配引发崩溃。 - 循环中反复调用
cancelAll()后添加新任务,会残留已取消但未清理的异步请求,同时self.activeChain在异步任务执行过程中可能已变更,导致请求参数错误。 - 强制解包
contractAddress!会在地址为空时直接崩溃。
解决方案步骤
1. 让Web3Wrapper支持任务取消
修改Web3Wrapper.shared.processValue方法,使其响应Swift Concurrency的任务取消信号,并终止对应的Alamofire请求:
// Web3Wrapper中的processValue方法示例 func processValue(contractAddress: String, value: String, chainId: String, rpcUrl: String) async throws -> [String: Any] { // 检查任务是否已取消,提前终止 try Task.checkCancellation() return try await withCheckedThrowingContinuation { continuation in let request = AF.request(rpcUrl, method: .post, parameters: ["chainId": chainId, "contractAddress": contractAddress, "value": value]) .responseJSON { response in // 再次检查任务取消状态 if Task.isCancelled { continuation.resume(throwing: CancellationError()) return } switch response.result { case .success(let data): continuation.resume(returning: data as? [String: Any] ?? [:]) case .failure(let error): continuation.resume(throwing: error) } } // 绑定任务取消到Alamofire请求取消 Task { [weak request] in try await Task.sleep(nanoseconds: 0) if Task.isCancelled { request?.cancel() } } } }
2. 修正Task Group的使用逻辑
你的需求是发起新API调用时终止所有之前的请求,当前循环中反复创建任务的逻辑冗余,只需保留最后一个符合条件的请求即可,同时避免Task Group的不必要使用:
func processHistoryDetails(historyDetail: History) async -> [String: Any]? { // 筛选所有包含"value"参数的日志项 guard let validLogs = historyDetail.logHistory?.filter({ log in log.decodedEvent?.params?.contains(where: { $0.name == "value" }) ?? false }) else { return nil } // 只处理最后一个符合条件的项(新请求终止旧请求,最后一个即为最新需要的) guard let targetLog = validLogs.last, let contractAddress = targetLog.address, let valueIndex = targetLog.decodedEvent?.params?.firstIndex(where: { $0.name == "value" }), let value = targetLog.decodedEvent?.params?[valueIndex].value else { return nil } // 提前捕获当前链参数,避免异步执行时链切换导致参数错误 let currentChainId = activeChain.chainId let currentRpcUrl = activeChain.rpcUrl do { return try await Web3Wrapper.shared.processValue( contractAddress: contractAddress, value: value, chainId: currentChainId, rpcUrl: currentRpcUrl ) } catch is CancellationError { // 任务被取消时返回nil或自定义处理逻辑 return nil } catch { print("Web3调用失败: \(error)") return nil } }
3. 全局请求终止逻辑(可选)
如果需要在切换链时主动终止所有未完成的Web3请求,可以给Web3Wrapper添加全局取消机制:
class Web3Wrapper { static let shared = Web3Wrapper() private var requestCancellables = Set<AnyCancellable>() func cancelAllRequests() { requestCancellables.forEach { $0.cancel() } requestCancellables.removeAll() } func processValue(contractAddress: String, value: String, chainId: String, rpcUrl: String) async throws -> [String: Any] { try Task.checkCancellation() return try await withCheckedThrowingContinuation { continuation in let request = AF.request(rpcUrl, method: .post, parameters: ["chainId": chainId, "contractAddress": contractAddress, "value": value]) .responseJSON { [weak self] response in guard let self = self else { return } if Task.isCancelled || self.requestCancellables.isEmpty { continuation.resume(throwing: CancellationError()) return } switch response.result { case .success(let data): continuation.resume(returning: data as? [String: Any] ?? [:]) case .failure(let error): continuation.resume(throwing: error) } } // 将请求的取消令牌存入集合 requestCancellables.insert(request.cancelable) } } } // 切换链时调用 Web3Wrapper.shared.cancelAllRequests()
关键说明
- 任务取消必须从Alamofire请求底层到Swift Concurrency层面做完整处理,否则已取消的任务仍会占用资源并引发错误。
- 提前捕获链参数可以避免异步任务执行期间链切换导致的参数不匹配问题。
- 避免强制解包,通过可选绑定确保所有参数非空再执行请求。
内容的提问来源于stack exchange,提问作者Muhammad Bilal Raza
相关产品推荐
相关产品推荐

