Reputation: 407
I'm trying to compile this simple example, first Xcode 14.3 (Swift 5.8) tells me
No such module 'Concurrency'
Then if I change that line to import _Concurrency, Xcode 14.3 tells me
Cannot find 'withTimeout' in scope
same is true using 'withDeadline()'. What is wrong?
import Foundation
import Concurrency
func fetchImageData() async throws -> Data {
return try await withTimeout(1) {
// Simulate a long-running async operation
let imageData = try await URLSession.shared.data(from: URL(string: "https://example.com/image.jpg")!)
return imageData.0
}
}
Upvotes: 0
Views: 493
Reputation: 438487
There is no need to import
anything for Swift concurrency, as that is imported for you.
But there is no withTimeout
function. You could write one, though. Perhaps:
extension Task where Failure == Error {
/// Run asynchronous task, but cancel it if not finished within a certain period of time.
///
/// - Parameters:
/// - duration: The duration before the task is canceled.
/// - operation: The async closure to run. The code therein should support cancelation.
/// - Throws: If canceled, will throw `CancellationError`. If the task fails, this will throw the task’s error.
/// - Returns: This will return the value returned by the `async` closure.
static func withTimeout(_ duration: Duration, operation: @Sendable @escaping () async throws -> Success) async rethrows -> Success {
let operationTask = Task.detached {
try await operation()
}
let cancelationTask = Task<Void, Error>.detached {
try await Task<Never, Never>.sleep(for: duration)
operationTask.cancel()
}
return try await withTaskCancellationHandler {
defer { cancelationTask.cancel() }
return try await operationTask.value
} onCancel: {
operationTask.cancel()
}
}
}
Needless to say, I replaced the TimeInterval
(i.e., seconds) with a more general Duration
and made it a static
method of Task
, but the overall idea is simple: Basically, start one task for the supplied closure, another for the cancelation of that other task, and whichever finishes first will cancel the other. And obviously, if the withTimeout
, itself, is canceled, then cancel the unstructured concurrency tasks, too.
But do not get lost in the weeds of this, as there are probably many variations on the theme one might consider.
Then you could call it like so:
func fetchImageData(from url: URL) async throws -> Data {
try await Task.withTimeout(.seconds(1)) {
try await URLSession.shared.data(from: url).0
}
}
All of that having been said, URLSession
/URLRequest
both already have a timeout parameter, so you might as well use that, perhaps:
nonisolated func fetchImageData(from url: URL) async throws -> Data {
let request = URLRequest(url: url, timeoutInterval: 1)
return try await URLSession.shared.data(for: request).0
}
Upvotes: 1