You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启动新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
    }
}

关键修改点说明

  1. 替换sleep方法:用try await Task.sleep(nanoseconds: 1_000_000_000)替代sleep(1),这个方法会在Task被取消时立即抛出CancellationError,终止任务执行。
  2. 处理取消异常:在任务内部捕获取消错误,返回失败结果;在ViewController里忽略主动取消带来的错误,只处理真正的请求异常。
  3. 增加取消检查:在sleep完成后额外检查Task.isCancelled,避免极端情况下任务在sleep结束后被取消,仍返回旧结果。

现在连续点击按钮时,前一个任务会被立即取消,只有最后一次点击的任务会完成并弹窗显示最新的counter值。

内容的提问来源于stack exchange,提问作者Kerim Khasbulatov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 06:02:05