启动新async/await Task前取消上一个任务失效问题排查
问题解决:Swift Task取消无效的修复方案
问题根源
你代码里用的sleep(1)是POSIX阻塞函数,完全不响应Swift Concurrency的Task取消信号。所以即使你调用了task?.cancel(),前一个任务里的sleep(1)还是会完整执行1秒,最终返回结果"1",导致弹窗不符合预期。
修复方案
把阻塞的sleep(1)替换成Swift Concurrency原生的Task.sleep(nanoseconds:),这个方法支持取消;同时处理取消异常,确保被取消的任务不会返回有效结果。
修改后的完整代码
import UIKit import SnapKit import Foundation class ViewController: UIViewController { lazy var button: UIButton = { let btn = UIButton() btn.setTitle("tap button", for: .normal) btn.backgroundColor = .systemGreen btn.addTarget(self, action: #selector(tapButtonAction), for: .touchUpInside) return btn }() var counter = 0 var fooClass = FooClass() func showAlert(_ title: String) { let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupView() } func setupView() { view.addSubview(button) button.snp.makeConstraints { $0.height.equalTo(48) $0.width.equalTo(128) $0.center.equalToSuperview() } } @objc func tapButtonAction() { counter += 1 Task { guard let result = await fooClass.fetch(counter) else { return } switch result { case let .success(number): print(number) showAlert(number) case let .failure(error): // 主动取消的任务,直接忽略错误 if !(error is CancellationError) { print(error.localizedDescription) } } } } } class FooClass { private var task: Task<Result<String, Error>, Never>? func fetch(_ counter: Int) async -> Result<String, Error>? { // 取消前一个任务 task?.cancel() // 创建新任务 task = Task { do { // 使用可取消的异步sleep,替换阻塞的sleep(1) try await Task.sleep(nanoseconds: 1_000_000_000) // 再次检查是否被取消(避免sleep后任务被取消的极端情况) guard !Task.isCancelled else { throw CancellationError() } return .success("\(counter)") } catch { return .failure(error) } } return await task?.value } }
关键修改点说明
- 替换sleep方法:用
try await Task.sleep(nanoseconds: 1_000_000_000)替代sleep(1),这个方法会在Task被取消时立即抛出CancellationError,终止任务执行。 - 处理取消异常:在任务内部捕获取消错误,返回失败结果;在ViewController里忽略主动取消带来的错误,只处理真正的请求异常。
- 增加取消检查:在sleep完成后额外检查
Task.isCancelled,避免极端情况下任务在sleep结束后被取消,仍返回旧结果。
现在连续点击按钮时,前一个任务会被立即取消,只有最后一次点击的任务会完成并弹窗显示最新的counter值。
内容的提问来源于stack exchange,提问作者Kerim Khasbulatov
相关产品推荐
相关产品推荐

