How to cancel a Task 如何取消任务

How to cancel a Task 如何取消任务

温馨提示:本文最后更新于2025-02-08 12:31:40,某些文章具有时效性,若有错误或已失效,请在下方留言

Swift’s tasks use cooperative cancellation, which means that although we can tell a task to stop work, the task itself is free to completely ignore that instruction and carry on for as long as it wants. This is a feature rather than a bug: if cancelling a task made it stop work immediately, the task might leave your program in an inconsistent state.
Swift 的任务使用协作取消,这意味着尽管我们可以告诉任务停止工作,但任务本身可以自由地完全忽略该指令并继续进行,只要它愿意。这是一个功能,而不是一个错误:如果取消任务会使其立即停止工作,则该任务可能会使您的程序处于不一致的状态。

There are seven things to know when working with task cancellation:
使用任务取消时,需要了解七件事:

  1. You can explicitly cancel a task by calling its cancel() method.
    您可以通过调用任务的 cancel() 方法显式取消任务。
  2. Any task can check Task.isCancelled to determine whether the task has been cancelled or not.
    任何任务都可以检查 Task.isCancelled 以确定任务是否已被取消。
  3. You can call the Task.checkCancellation() method, which will throw a CancellationError if the task has been cancelled or do nothing otherwise.
    您可以调用 Task.checkCancellation() 方法,如果任务已被取消,它将引发 CancellationError 或不执行任何作。
  4. Some parts of Foundation automatically check for task cancellation and will throw their own cancellation error even without your input.
    Foundation 的某些部分会自动检查任务取消,即使没有您的输入,也会抛出自己的取消错误。
  5. If you’re using Task.sleep() to wait for some amount of time to pass, cancelling your task will automatically terminate the sleep and throw a CancellationError.
    如果你使用 Task.sleep() 等待一段时间过去,取消你的任务将自动终止睡眠并抛出 CancellationError
  6. If the task is part of a group and any part of the group throws an error, the other tasks will be cancelled and awaited.
    如果任务是组的一部分,并且组的任何部分引发错误,则其他任务将被取消并等待。
  7. If you have started a task using SwiftUI’s task() modifier, that task will automatically be canceled when the view disappears.
    如果您使用 SwiftUI 的 task() 修饰符启动了任务,则该任务将在视图消失时自动取消。

We can explore a few of these in code. First, here’s a function that uses a task to fetch some data from a URL, decodes it into an array, then returns the average:
我们可以在代码中探索其中的一些。首先,这是一个函数,它使用任务从 URL 中获取一些数据,将其解码为数组,然后返回平均值:

func getAverageTemperature() async {
    let fetchTask = Task {
        let url = URL(string: "https://hws.dev/readings.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        let readings = try JSONDecoder().decode([Double].self, from: data)
        let sum = readings.reduce(0, +)
        return sum / Double(readings.count)
    }

    do {
        let result = try await fetchTask.value
        print("Average temperature: \(result)")
    } catch {
        print("Failed to get data.")
    }
}

await getAverageTemperature()
下载图标
how-to-cancel-a-task-1.zip
zip文件
49.5K

Now, there is no explicit cancellation in there, but there is implicit cancellation because the URLSession.shared.data(from:) call will check to see whether its task is still active before continuing. If the task has been cancelled, data(from:) will automatically throw a URLError and the rest of the task won’t execute.
现在,那里没有显式取消,但存在隐式取消,因为 URLSession.shared.data(from:) 调用将在继续之前检查其任务是否仍处于活动状态。如果任务已被取消,data(from:) 将自动抛出 URLError,并且任务的其余部分不会执行。

However, that implicit check happens before the network call, so it’s unlikely to be an actual cancellation point in practice. As most of our users are likely to be using mobile network connections, the network call is likely to take most of the time of this task, particularly if the user has a poor connection.
但是,该隐式检查发生在 network 调用之前,因此在实践中不太可能是实际的取消点。由于我们的大多数用户可能正在使用移动网络连接,因此网络调用可能会占用此任务的大部分时间,尤其是在用户连接不佳的情况下。

So, we could upgrade our task to explicitly check for cancellation after the network request, using Task.checkCancellation(). This is a static function call because it will always apply to whatever task it’s called inside, and it needs to be called using try so that it can throw a CancellationError if the task has been cancelled.
因此,我们可以升级我们的任务,以使用 Task.checkCancellation() 在网络请求显式检查是否取消。这是一个静态函数调用,因为它将始终应用于它在内部调用的任何任务,并且需要使用 try 调用它,以便在任务被取消时可以引发 CancellationError

Here’s the new function:  以下是新功能:

func getAverageTemperature() async {
    let fetchTask = Task {
        let url = URL(string: "https://hws.dev/readings.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        try Task.checkCancellation()
        let readings = try JSONDecoder().decode([Double].self, from: data)
        let sum = readings.reduce(0, +)
        return sum / Double(readings.count)
    }

    do {
        let result = try await fetchTask.value
        print("Average temperature: \(result)")
    } catch {
        print("Failed to get data.")
    }
}

await getAverageTemperature()
下载图标
how-to-cancel-a-task-2.zip
zip文件
49.5K

As you can see, it just takes one call to Task.checkCancellation() to make sure our task isn’t wasting time calculating data that’s no longer needed.
如你所见,只需调用 Task.checkCancellation() 即可确保我们的任务不会浪费时间计算不再需要的数据。

If you want to handle cancellation yourself – if you need to clean up some resources or perform some other calculations, for example – then instead of calling Task.checkCancellation() you should check the value of Task.isCancelled instead. This is a simple Boolean that returns the current cancellation state, which you can then act on however you want.
如果您想自己处理取消 – 例如,如果您需要清理一些资源或执行一些其他计算 – 那么您应该检查 Task.isCancelled 的值,而不是调用 Task.checkCancellation() 。这是一个简单的布尔值,它返回当前的取消状态,然后你可以根据需要对其进行作。

To demonstrate this, we could rewrite our function a third time so that cancelling the task or failing to fetch data returns an average temperature of 0. This time we’re going to cancel the task ourselves as soon as it’s created, but because we’re always returning a default value we no longer need to handle errors when reading the task’s result:
为了证明这一点,我们可以第三次重写我们的函数,以便取消任务或无法获取数据返回平均温度 0。这一次,我们将在任务创建后立即自行取消任务,但是因为我们总是返回一个默认值,所以在读取任务结果时不再需要处理错误:

func getAverageTemperature() async {
    let fetchTask = Task {
        let url = URL(string: "https://hws.dev/readings.json")!

        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            if Task.isCancelled { return 0.0 }

            let readings = try JSONDecoder().decode([Double].self, from: data)
            let sum = readings.reduce(0, +)
            return sum / Double(readings.count)
        } catch {
            return 0.0
        }
    }

    fetchTask.cancel()

    let result = await fetchTask.value
    print("Average temperature: \(result)")
}

await getAverageTemperature()
下载图标
how-to-cancel-a-task-3.zip
zip文件
49.5K

Tip: That uses return 0.0 rather than return 0 so that Swift can correctly infer the task’s return type as a Double. If you wanted to avoid that, you could tell Swift explicitly that a Double is coming back using Task { () -> Double in.
提示: 它使用 return 0.0 而不是 return 0,以便 Swift 可以正确地将任务的返回类型推断为 Double。如果你想避免这种情况,你可以明确地告诉 Swift 一个 Double 即将回来,使用 Task { () -> Double in

Now we have one implicit cancellation point with the data(from:) call, and an explicit one with the check on Task.isCancelled. If either one is triggered, the task will return 0 rather than throw an error.
现在,我们有一个带有 data(from:) 调用的隐式取消点,以及一个带有 Task.isCancelled 检查的显式取消点。如果触发了其中任何一个,则任务将返回 0 而不是引发错误。

Tip: You can use both Task.checkCancellation() and Task.isCancelled from both synchronous and asynchronous functions. Remember, async functions can call synchronous functions freely, so checking for cancellation can be just as important to avoid doing unnecessary work.
提示: 您可以从同步和异步函数中使用 Task.checkCancellation() 和 Task.isCancelled。请记住,异步函数可以自由调用同步函数,因此检查取消对于避免执行不必要的工作同样重要。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享