您的位置:首页 > 文旅 > 美景 > Swift concurrency 4 — Task和.task的理解与使用

Swift concurrency 4 — Task和.task的理解与使用

2024/12/23 1:46:34 来源:https://blog.csdn.net/guoyongming925/article/details/140917114  浏览:    关键词:Swift concurrency 4 — Task和.task的理解与使用

Task

Swift中的Task是一种异步操作,它提供了一种替代DispatchQueue.main.async{}等传统方法的方法。通过使用Task,我们可以简化代码并更好地控制异步操作。此外,Task还提供了其他选项,可以进一步增强任务执行。

先看一个Task的基本使用:

class TaskViewModel: ObservableObject {@Published var image: UIImage? = nilfunc fetchImage() async {do {guard let url = URL(string: "https://picsum.photos/1000") else { return }let (data, _) = try await URLSession.shared.data(from: url)await MainActor.run {self.image = UIImage(data: data)print("---> 图片请求成功!")}} catch {print("\(error.localizedDescription)")}}
}struct TaskViewDemo: View {@StateObject private var viewModel = TaskViewModel()var body: some View {VStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {Task {await viewModel.fetchImage()}}}
}

上面代码中我们在TaskViewModel中请求一张图片,然后在View中显示,请求是在onAppear中使用了Task,前面的文章中也早就涉及过。

现在如果再请求一张图片,在同一个Task中请求两个图片,比如下面的代码:

class TaskViewModel: ObservableObject {@Published var image: UIImage? = nil@Published var image2: UIImage? = nilfunc fetchImage() async {do {guard let url = URL(string: "https://picsum.photos/1000") else { return }let (data, _) = try await URLSession.shared.data(from: url)await MainActor.run {self.image = UIImage(data: data)print("---> 图片请求成功!")}} catch {print("\(error.localizedDescription)")}}func fetchImage2() async {do {guard let url = URL(string: "https://picsum.photos/1000") else { return }let (data, _) = try await URLSession.shared.data(from: url)await MainActor.run {self.image2 = UIImage(data: data)print("---> 图片请求成功!")}} catch {print("\(error.localizedDescription)")}}
}struct TaskViewDemo: View {@StateObject private var viewModel = TaskViewModel()var body: some View {VStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}if let image = viewModel.image2 {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {Task {await viewModel.fetchImage()await viewModel.fetchImage2()}}}
}

在同一个Task里面又添加了一个图片的请求,那么执行顺序是怎么样的呢?相比大家也猜到了,第二个会在第一个请求回来后再去请求,毕竟有await在那呢。
请添加图片描述
现在对代码做一下改动,分别用两个Task去请求两个图片,调用如下:

.onAppear {Task {await viewModel.fetchImage()}Task {await viewModel.fetchImage2()}
}

请添加图片描述
从效果上看,两个Task之间是不存在相互依赖的关系的,两张图片几乎是同一时间请求回来的。

TaskPriority

SwiftUI 中,Task(priority:operation:) 是用于创建并启动异步任务的一种方式,其中 TaskPriority 参数用于指定任务的优先级。优先级决定了系统在调度和执行任务时如何分配资源,特别是在多任务执行时,优先级较高的任务可能会优先获得执行机会。

TaskPriority 是一个枚举类型,定义了任务的优先级。它包含以下几种常见的优先级选项:

  • .high:高优先级任务,通常用于需要立即响应的任务。例如,用户界面的更新任务。
  • .medium:中等优先级任务,适用于一般任务。默认情况下,许多任务会使用此优先级。
  • .low:低优先级任务,适用于可以延迟执行的任务。例如,后台数据同步或预加载任务。
  • .background:后台任务优先级,用于那些不需要用户立即注意的任务。适用于后台处理或预加载数据。
  • .userInitiated:用户发起的任务优先级,通常用于用户直接触发的任务,比如用户按下按钮后启动的操作。优先级略高于 .medium
  • .utility:实用任务优先级,适用于需要一定时间完成,但不需要立即响应的任务。比如下载数据或处理文件。

在这里插入图片描述
上面的图片中,我们用代码将每个优先级都打印了一下,通过数字可以更好的看出优先级顺序。
另外这里还要强调一下,不是说高优先级的一定会最先去执行,最终的调度仍然由系统管理。系统会根据当前的负载、资源可用性等因素综合决定任务的执行顺序。

在一个Task中再调用一个Task,那么子Task的优先级会继承父Task的优先级。
在这里插入图片描述
当不指定优先级的时候,默认优先级是high。现在我们指定一个并在内部再添加一个Task看看。
在这里插入图片描述
如果子Task未指定优先级,那么就会继承父一级的优先级。如果指定了就不继承了,比如下图:
在这里插入图片描述
如果子Task又不想继承父Task优先级,又不想指定优先级,那么可以用

Task.detached {print("child priority : \(Task.currentPriority)")
}

效果如下:
在这里插入图片描述
不过关于Task.detached方法,苹果也有特别的说明,如下:

/// Don’t use a detached task if it’s possible
/// to model the operation using structured concurrency features like child tasks.
/// Child tasks inherit the parent task’s priority and task-local storage,
/// and canceling a parent task automatically cancels all of its child tasks.
/// You need to handle these considerations manually with a detached task.
/// You need to keep a reference to the detached task
/// if you want to cancel it by calling the Task.cancel() method.
/// Discarding your reference to a detached task
/// doesn’t implicitly cancel that task,
/// it only makes it impossible for you to explicitly cancel the task.

从第一句话就可以看出苹果是不推荐用这个Task.detached方法的。

cancel(取消任务)

Task.cancel()Task 中的一个方法,用于请求取消正在运行的任务。了解如何使用 Task.cancel() 非常重要,尤其是在处理长时间运行的异步操作时,确保可以在需要时安全地停止这些操作。
Task.cancel() 是一个实例方法,用于标记任务为取消状态。当你调用 cancel() 方法时,你实际上是在通知任务:“你应该尽快停止工作。” 但是需要注意的是,调用 cancel() 并不会立即终止任务,而是设置一个取消标志(isCancelled 属性变为 true),然后任务内部代码需要定期检查这个标志,并自行决定是否中止任务。

如何正确使用 Task.cancel()

  1. 启动任务:
    可以使用 Task { ... } 来启动一个异步任务。
  2. 请求取消:
    当需要取消任务时,可以调用 cancel() 方法。
  3. 任务响应取消:
    任务内部应定期检查 Task.isCancelled 属性,确保在任务被取消时能够及时响应,并终止正在执行的操作。

还是接着上面的代码说,我们将上面的代码放到导航栏的第二页,由主页面push出来,到第二个界面后请求图片的时候加上5秒延迟,并在onDisappear方法中调用cancel()方法,然后未到5秒的时候返回到第一个界面。代码如下:

func fetchImage() async {try? await Task.sleep(nanoseconds: 5_000_000_000)do {guard let url = URL(string: "https://picsum.photos/1000") else { return }let (data, _) = try await URLSession.shared.data(from: url)await MainActor.run {self.image = UIImage(data: data)print("---> 图片请求成功!")}} catch {print("\(error.localizedDescription)")}
}

上面代码添加了5秒的延迟。

struct TaskViewDemo: View {@StateObject private var viewModel = TaskViewModel()@State private var fetchImageTask: Task<(), Never>? = nilvar body: some View {VStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {fetchImageTask = Task {await viewModel.fetchImage()}}.onDisappear {fetchImageTask?.cancel()}}
}

上面代码中定义了一个fetchImageTask实例变量,用来持有Task实例,并在onDisappear方法调用cancel()方法。
请添加图片描述
很明显,在未到5秒的时候返回,控制台能看到系统的已取消输出。
但是实际开发中很少有这种等几秒的情况,如果在ViewModel中有循环处理事件,那么就要判断一下当前的任务是否已经取消了

Task.checkCancellation()

调用checkCancellation()方法后,如果任务中途取消了,那么该方法将抛出异常,代码块也不会再继续执行了。调用时如果不想处理异常错误,直接使用try?即可:

try? Task.checkCancellation()

简单示例如下:

for work in workArray {// 检查是否已经取消try? Task.checkCancellation()// 任务代码// ......
}

还要说一下,定义Task实例的时候,因为Task是泛型,需要指定SuccessFailure

@frozen public struct Task<Success, Failure> : Sendable where Success : Sendable, Failure : Error {
}

.task(SwiftUI)

.task 修饰符用于在视图的生命周期中启动一个异步任务。这些任务会在视图出现时自动启动,并且可以与 Swift 的并发功能一起使用,如 asyncawait

.task(priority: TaskPriority? = nil, _ action: @escaping () async -> Void) -> some View
  • priority:可选的任务优先级。可以是 .low, .medium, .high, .userInitiated,.background 等。默认为 nil,表示使用系统默认优先级。
  • action:在视图出现时执行的异步代码块。

使用场景

  • 视图加载时初始化数据:在视图加载时,使用 .task 修饰符来启动异步数据加载或初始化任务。
  • 执行后台操作:启动一些不影响 UI 但需要后台处理的任务,例如分析数据、同步远程服务器等。

比如刚才的示例中:

.task {await viewModel.fetchImage()
}

.task 修饰符使得在 SwiftUI 中处理异步任务变得更加简洁和直观,特别是在与 Swift 的并发功能结合使用时,能够轻松处理复杂的异步操作。

注意事项

  • 任务取消:.task 启动的任务会在视图被移除时自动取消。因此,如果视图在任务完成之前被移除,该任务将不会继续运行。
  • .onAppear 的区别:.task 类似于 .onAppear,但 .task 专门用于启动异步任务,并且能够更好地处理 Swift并发模型。.onAppear 通常用于启动同步代码或不需要异步处理的逻辑。
  • 任务优先级:可以使用 priority 参数来设置任务的优先级,但这通常在需要优化性能或资源分配时才需要考虑。

小结

本篇文章主要介绍了Task的基本使用,优先级,以及取消等功能,在SwiftUI中也可以使用.task修饰符来代替Task,而且视图销毁的时候还能自动cancel任务。关于Task.task的使用,想必大家通过这篇文章也有所了解了。感谢大家的阅读。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com