swift 多任务中的一个方法async/await

omhiaaxx  于 2023-09-30  发布在  Swift
关注(0)|答案(2)|浏览(146)

你好,我有一个案例,我需要在多个任务中调用相同的方法。我希望有一个可能性调用这个方法一个接一个(同步),而不是在并行模式。看起来像这样:

var isReadyToRefresh: Bool = true

func refresh(value: Int) async {
    try! await Task.sleep(nanoseconds: 100_000_000) // imitation API CALL
    isReadyToRefresh = false
    print("Try to refresh: \(value)")
}

func mockCallAPI(value: Int) async {
    if isReadyToRefresh {
        await refresh(value: value)
    }
}

Task {
     await mockCallAPI(value: 1)
}

Task {
     await mockCallAPI(value: 2)
}

产出:
尝试刷新:1
尝试刷新:2
我需要的输出:
尝试刷新:% 1或尝试刷新% 2。取决于哪个任务被作为第一个任务调用。
有什么想法吗?

vmdwslir

vmdwslir1#

你说:
我希望[第二次尝试]等待第一次刷新API完成
您可以保存对Task的引用,如果找到,则await它。如果没有找到,则启动任务。(因为我们使用的是非结构化并发,所以请记住将其 Package 在withTaskCancellationHandler中。
另外,我个人会将关于等待/取消的逻辑移动到“刷新”过程中,而不是API调用代码中。由此可见:

actor Refresh {
    var priorTask: Task<Void, Error>?

    func refresh(value: Int) async throws {
        if let priorTask {
            _ = try await priorTask.value
            return
        }

        let task = Task {
            try await mockCallAPI(value: value)
        }

        priorTask = task

        try await withTaskCancellationHandler {
            _ = try await task.value
            priorTask = nil
        } onCancel: {
            task.cancel()
        }
    }

    private func mockCallAPI(value: Int) async throws {
        try await Task.sleep(for: .seconds(0.1))        // imitation API CALL
        print("Try to refresh: \(value)")
    }
}

苹果在与WWDC 2021视频相关的代码中展示了这种模式的一个例子,Protect mutable state with Swift actors
他们的例子更复杂(一种避免重复网络请求被某些图像缓存/下载器发起的模式),但其思想的核心是相同的:保存并awaitTask
请注意,上面是围绕原始问题设计的,以返回第一个请求的结果,并避免在前一个请求正在进行时启动后续请求。这种模式在缓存结果模式中很常见,在这种模式中,您可能会有重复的请求,所有这些请求都返回完全相同的结果(例如,来自CDN的一些静态资源,例如Apple示例中的静态资源)。
但是当我们谈论“刷新”过程时,用户通常想要最新的结果。使用“刷新”,我们通常不想向用户显示先前请求的较旧的、可能过期的结果。因此,在刷新时,我们通常希望取消先前的请求并启动新的请求:

actor Refresh {
    var priorTask: Task<Void, Error>?

    func refresh(value: Int) async throws {
        let task = Task { [priorTask] in
            priorTask?.cancel()
            try await mockCallAPI(value: value)
        }

        priorTask = task

        try await withTaskCancellationHandler {
            _ = try await task.value
        } onCancel: {
            task.cancel()
        }
    }

    private func mockCallAPI(value: Int) async throws {…}
}

这一点很微妙,但请注意,我们希望捕获priorTask,以避免多次调用该刷新进程之间的竞争。
所以,你有这两个选择:(a)等待/返回第一个请求并避免重复请求;或者(B)取消先前的请求并发起新的请求,确保刷新返回最新的结果。一般来说,当结果是静态的时,我们倾向于第一种模式,当结果可能随时间变化时,我们倾向于第二种模式。使用术语“刷新”通常意味着我们想要最新的结果,但这完全取决于您。我只想展示这两种模式。

rekjcdws

rekjcdws2#

如果你不想让它们并行运行,为什么它们需要在单独的任务中呢?await意味着代码在任务完成后不会有任何进展,因为协作线程启动它的线程可能会被用来做其他事情,比如更多地使用交互处理,或者其他任务,事实上,因为你把它们放在单独的任务中,你要求它们并行运行,包含它们的块可能会进入非常bringing,并且您将有另外两个任务,在这种情况下,您需要等待来自它们容器任务的结果,说它们已经完成,并且进入块的代码检查此以继续任何进一步。

相关问题