Swift: Task + DispatchTimeInterval
Improving Task.sleep for everyone.
Swift: Improving Task.sleep
Motivation
Task.sleep(nanoseconds:)
is pretty awkward to use, as it necessitates use of UInt64
to specify a time with nanosecond precision. This precision is nice when you are working with very small slices of time, but it's less useful when dealing with timeouts measured in seconds.
Task.sleep
Let's fix it up to accept the highly-useful DispatchTimeInterval
enum as an argument instead..
public extension Task where Success == Never, Failure == Never {
static func sleep(_ timeInterval: DispatchTimeInterval) async throws {
let duration: UInt64
switch timeInterval {
case let .seconds(s):
duration = UInt64(s) * 1_000_000_000
case let .milliseconds(ms):
duration = UInt64(ms) * 1_000_000
case let .microseconds(us):
duration = UInt64(us) * 1_000
case let .nanoseconds(ns):
duration = UInt64(ns) * 1
case .never:
duration = .max
@unknown default:
fatalError("Unknown DispatchTimeInterval case in Task.sleep(_:)")
}
try await Task.sleep(nanoseconds: duration)
}
}
Note that we must constrain the extension to where Success == Never, Failure == Never
in order to satisfy the requirements of Task.sleep(nanoseconds:)
, the documentation of which states:
Available when Success is Never and Failure is Never.
This is because Task
takes two generic parameters Success
and Failure
, and the sleep(nanoseconds:)
function is only availale for Task<Never,Never>
.
Now, let's see our function being used:
print("sleeping for 1 second...")
try await Task.sleep(.seconds(1))
print("running task!")
Well, that was easy!
Task.after
We can also create a convenience method to run a task after
a delay, using our new sleep
function.
extension Task where Failure == Error {
static func after(
_ timeInterval: DispatchTimeInterval,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async throws -> Success
) -> Task {
Task(priority: priority) {
try await Task<Never,Never>.sleep(timeInterval)
return try await operation()
}
}
}
This time, instead of just waiting, we have an operation that we need to call after the delay.
Note that we have different constraints on the extension this time. We don't have the Never
constraints, because we're only using the sleep
task as a subtask, but we do have a Failure == Error
constraint, because we have an operation
that could potentially fail. Also note how we had to specify Task<Never,Never>
in order to call sleep(_:)
from this extension, because here Task
refers to Task<s,Error>
where the Success
type s
is undetermined.
Using this function is nice and easy too!
print("running task after 1 second...")
try await Task.after(.seconds(1)) {
print("running task!")
}
No more mucking about with nanosecond conversion!