Swift 两种方式实现 async/await 并发模型中任务超时(timeout)的取消

Posted 大熊猫侯佩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 两种方式实现 async/await 并发模型中任务超时(timeout)的取消相关的知识,希望对你有一定的参考价值。

文章目录

1. 概览

从 Swift 5.5 开始,Apple 引入了新的 async/await 并发模型,我们可以利用它很方便的开发结构化并发代码。

在使用新的并发模型时,一个常见的需求就是任务超时的处理。

我们希望当耗时任务的执行在到达指定时间后自动取消,以免影响用户体验。

在本篇博文中,我们将用两种方法来实现新并发模型中 Task 执行的超时处理。

废话少叙,Let’s Go!!!😉


2. Task 超时取消实现之思路

在 Swift 新 async/await 并发模型中,每个 Task 都会并发执行。所以,要想实现任务超时处理我们只需要同时执行两个任务:

  1. 第一个是需要完成的任务;
  2. 第二个是超时监控任务,用它来监听第一个任务是否已超时,如果是则果断取消它;

有的小伙伴可能觉得实现监控任务有点棘手,其实,这超乎意料的简单!

3. 第一种实现

我们只需保存执行的 Task 对象,然后在超时发生时,在第二个超时监控任务中将其取消即可,代码如下:

struct TimedOutError: Error, Equatable 

func execute<R>(timeout: TimeInterval, task: @escaping () async throws -> R) async throws -> R 
        
    let task = Task 
        try await task()
    
    
    Task 
        try? await Task.sleep(until: .now + .seconds(timeout), clock: .continuous)
        task.cancel()
    
    
    do 
        return try await task.value
    catch
    	// 如果任务被取消,则我们知道任务超时了
        if task.isCancelled 
            throw TimedOutError()
        
        throw error
    

正如之前的实现思路,我们依次创建了两个任务:第一个是需要完成的任务,第二个则是超时监控任务;在第二个任务中,一旦超时到达,则取消第一个任务。

Task 
    do 
        let r = try await execute(timeout: 3.0)
        	// 任务会触发超时,将被取消!
            try await Task.sleep(until: .now + .seconds(3.1), clock: .continuous)
            return 11
        
        
        print("结果是 \\(r)")
    catch
        print("ERR: \\(error.localizedDescription)")
    

不过,这样有两个小问题:

  1. 如果第一个任务在超时前完成,超时监控任务在超时后仍会尝试取消它;
  2. 当任务执行出现异常时,我们需要判断任务是否被取消,然后再抛出 TimedOutError 错误;

虽然,这并不影响整个 execute() 方法的逻辑,但如果小伙伴们是强迫症患者,我们还有第二种方法可以选择。

4. 第二种实现

第二种方法和前者类似,不过这里我们利用了新并发模型中任务组(TaskGroup)的特点:其中所有子任务都会并发执行,而且我们可以取消所有子任务。

func execute2<R>(timeout: TimeInterval, task: @escaping () async throws -> R) async throws -> R 
    try await withThrowingTaskGroup(of: R.self)  group in
        group.addTask 
            try await task()
        
        
        group.addTask 
            try await Task.sleep(until: .now + .seconds(timeout), clock: .continuous)
            throw TimedOutError()
        
        
        let result = try await group.next()!
        // 一旦我们取消所有子任务,则超时监控任务不会在正常任务未超时还抛出异常
        group.cancelAll()
        return result
    

如上代码所示,用 TaskGroup 来实现任务超时逻辑更加清晰,而且避免了第一种方法中的两个问题。


更多 Task 任务超时的讨论请参考下面的链接:

Running an async task with a timeout


5. 总结

在本篇博文中,我们用两种方法实现了 Swift 新 async/await 并发模型中任务超时的取消,任君选择。

感谢观赏,再会!😎

以上是关于Swift 两种方式实现 async/await 并发模型中任务超时(timeout)的取消的主要内容,如果未能解决你的问题,请参考以下文章

Swift 两种方式实现 async/await 并发模型中任务超时(timeout)的取消

Vue 两种处理异步的方式 Promise Async/Await

Swift新async/await并发模型中子任务取消不能被其它子任务感知的原因及解决

Swift新async/await并发模型中子任务取消不能被其它子任务感知的原因及解决

Swift 中的 async/await ——代码实例详解

图解 Swift async/await