如何为等待函数调用添加超时

Bic*_*ops 4 concurrency async-await swift

向等待函数添加超时的最佳方法是什么?

Example:

/// lets pretend this is in a library that I'm using and I can't mess with the guts of this thing
func fetchSomething() async -> Thing? {
    // fetches something
}

// if fetchSomething() never returns then doSomethingElse() is never ran. Is there anyway to add a timeout to this system?
let thing = await fetchSomething()
doSomethingElse()

Run Code Online (Sandbox Code Playgroud)

我想让系统在 fetchSomething() 永远不会返回的情况下更加健壮。如果这是使用组合,我会使用超时运算符。

Rob*_*Rob 5

可以创建一个Taskcancel如果在某一段时间内没有完成则再创建一个 。例如,并行启动两个任务:

\n
// cancel the fetch after 2 seconds\n\nfunc fetchThingWithTimeout() async throws -> Thing {\n    let fetchTask = Task {\n        try await self.fetchThing()                           // start fetch\n    }\n\n    let timeoutTask = Task {\n        try await Task.sleep(for: .seconds(2))                // timeout in 2 seconds\n        fetchTask.cancel()\n    }\n\n    return try await withTaskCancellationHandler {            // handle cancelation by caller of `fetchThingWithTimeout`\n        let result = try await fetchTask.value\n        timeoutTask.cancel()\n\n        return result\n    } onCancel: {\n        fetchTask.cancel()\n        timeoutTask.cancel()\n    }\n}\n\n// here is a random mockup that will take between 1 and 3 seconds to finish\n\nfunc fetchThing() async throws -> Thing {\n    let duration: TimeInterval = .random(in: 1...3)\n    try await Task.sleep(for: .seconds(duration))\n    return Thing()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果fetchTask先完成,它将到达timeoutTask.cancel并停止它。如果timeoutTask先完成,它将取消fetchTask.

\n

显然,这取决于函数的实现fetchThing。它不仅应该检测取消,还应该CancellationError在取消时抛出错误(可能是 )。如果没有有关实施的细节,我们无法进一步发表评论fetchTask

\n

例如,在上面的示例中,Thing?我不会返回可选的,而是返回Thing,但如果它被取消,则会出现throw错误。

\n
\n

请注意,这withTaskCancellationHandler是必需的,因为我们使用非结构化并发,其中取消不会自动为我们传播。我们必须手动处理这个问题。或者,您可以使用任务组保持结构化并发:

\n
func fetchThingWithTimeout() async throws -> Thing {\n    try await withThrowingTaskGroup(of: Thing.self) { group in\n        group.addTask {\n            try await self.fetchThing()                       // start fetch\n        }\n\n        group.addTask {\n            try await Task.sleep(for: .seconds(2))            // timeout in 2 seconds\n            throw CancellationError()\n        }\n\n        guard let value = try await group.next() else {       // see if fetch succeeded \xe2\x80\xa6\n            throw FetchError.noData                           // theoretically, it should not be possible to get here (as we either return a value or throw an error), but just in case\n        }\n\n        group.cancelAll()                                     // \xe2\x80\xa6 but if we successfully fetched a value, cancel the timeout task, and\n        return value                                          // \xe2\x80\xa6 return value\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我犹豫是否要提及它,但是虽然上面假设fetchThing行为良好(即可取消),但即使不起作用,模式上也有一些排列可以工作(即,doSomethingElse即使fetchThing\xe2\x80\在某个合理的时间表中运行) x9c 永远不会返回\xe2\x80\x9d)。

\n

但这本质上是一种不稳定的情况,因为fetchThing在完成之前所使用的资源无法恢复。Swift 不提供抢先取消,因此虽然我们可以轻松解决确保doSomethingElse最终运行的战术问题,但如果fetchThing可能永远不会在某个合理的时间表内完成,那么您会遇到更深层次的问题。

\n

你确实应该找到一个something可以取消的版本(如果还没有的话)。

\n