Home > Net >  How to add a timeout to an awaiting function call
How to add a timeout to an awaiting function call

Time:12-07

What's the best way to add a timeout to an awaiting function?

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()

I wanted to make the system more robust in the case that fetchSomething() never returns. If this was using combine, I'd use the timeout operator.

CodePudding user response:

One can create a Task, and then cancel it if it has not finished in a certain period of time. E.g., launch two tasks in parallel:

// cancel the fetch after 2 seconds

func fetchSomethingWithTimeout() async throws -> Thing {
    let fetchTask = Task {
        try await fetchSomething()
    }

    let timeoutTask = Task {
        try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
        fetchTask.cancel()
    }

    let result = try await fetchTask.value
    timeoutTask.cancel()

    return result
}

// here is a random mockup that will take between 1 and 3 seconds to finish

func fetchSomething() async throws -> Thing {
    let duration: TimeInterval = .random(in: 1...3)

    try await Task.sleep(nanoseconds: UInt64(TimeInterval(NSEC_PER_SEC) * duration))

    return Thing()
}

If the fetchTask finishes first, it will reach the timeoutTask.cancel and stop it. If timeoutTask finishes first, it will cancel the fetchTask.

Obviously, this rests upon the implementation of fetchTask. It should not only detect the cancelation, but also throw an error (likely a CancellationError) if it was canceled. We cannot comment further without details regarding the implementation of fetchTask.

For example, in the above example, rather than returning an optional Thing?, I would instead return Thing, but have it throw an error if it was canceled.


I hesitate to mention it, but while the above assumes that fetchSomething was well-behaved (i.e., cancelable), there are permutations on the pattern that work even if it does not (i.e., run doSomethingElse in some reasonable timetable even if fetchSomething “never returns”).

But this is an inherently unstable situation, as the resources used by fetchSomething cannot be recovered until it finishes. Swift does not offer preemptive cancelation, so while we can easily solve the tactical issue of making sure that doSomethingElse eventually runs, if fetchSomething might never finish in some reasonable timetable, you have deeper problem.

You really should find a rendition of fetchSomething that is cancelable, if it is not already.

CodePudding user response:

// You can use 'try and catch'. Wait for the fetch data inside the try block. When it fails the catch block can run a different statement. Something like this:

    await getResource() 
    try {
        await fetchData();
    } catch(err){
        doSomethingElse();
    }
    // program continues
  • Related