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

问题描述 投票:0回答:2

嗨,我有一个情况,我需要在多个任务中调用相同的方法。我希望能够一一调用此方法(同步)而不是在并行模式下。看起来像这样:

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。取决于第一个任务被调用。

有什么想法吗?

swift async-await task grand-central-dispatch urlsession
2个回答
7
投票

你说:

我想要[第二次尝试]等待第一次刷新API完成

您可以保存对您的

Task
的引用,如果找到,则
await
它。如果没有找到,则开始任务。 (因为我们使用的是非结构化并发,所以请记住将其包装在
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)")
    }
}

Apple 在与 WWDC 2021 视频相关的代码中展示了此模式的示例,使用 Swift Actor 保护可变状态

他们的示例更复杂(一种避免某些图像缓存/下载器发起重复网络请求的模式),但其思想的核心是相同的:保存并

await
Task


请注意,上面的内容是围绕原始问题设计的,以返回第一个请求的结果,并避免在前一个请求正在进行时启动后续请求。这种模式在缓存结果模式中很常见,在这种模式中,您可能有重复的请求,所有请求都返回完全相同的结果(例如,来自 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) 取消先前的请求并发起新的请求,确保刷新返回最新的结果。一般来说,当结果是静态时,我们会倾向于使用第一种模式,而当结果可能随时间变化时,我们会倾向于使用后者。使用术语“刷新”通常意味着我们想要最新的结果,但这完全取决于您。我只是想展示这两种模式。


-1
投票

如果您不希望它们并行运行,为什么它们需要处于单独的任务中,await 意味着代码在任务完成后不会再有任何进展,因为协作线程可以使用启动它的线程做其他事情,例如使用交互或其他任务处理更多内容,事实上,因为您将它们放在单独的任务中,所以您要求它们并行运行,因此包含它们的块可能会进入最前面,您将拥有另外两个任务,在这种情况下,您需要等待容器任务的结果,表明它们已完成,并且进入块的代码会检查此任务是否继续。

© www.soinside.com 2019 - 2024. All rights reserved.