Close Menu
  • Home
  • AI
  • Big Data
  • Cloud Computing
  • iOS Development
  • IoT
  • IT/ Cybersecurity
  • Tech
    • Nanotechnology
    • Green Technology
    • Apple
    • Software Development
    • Software Engineering

Subscribe to Updates

Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

    What's Hot

    How to run RAG projects for better data analytics results

    October 13, 2025

    MacBook Air deal: Save 10% Apple’s slim M4 notebook

    October 13, 2025

    Part 1 – Energy as the Ultimate Bottleneck

    October 13, 2025
    Facebook X (Twitter) Instagram
    Facebook X (Twitter) Instagram
    Big Tee Tech Hub
    • Home
    • AI
    • Big Data
    • Cloud Computing
    • iOS Development
    • IoT
    • IT/ Cybersecurity
    • Tech
      • Nanotechnology
      • Green Technology
      • Apple
      • Software Development
      • Software Engineering
    Big Tee Tech Hub
    Home»iOS Development»Implementing Task timeout with Swift Concurrency – Donny Wals
    iOS Development

    Implementing Task timeout with Swift Concurrency – Donny Wals

    big tee tech hubBy big tee tech hubMay 15, 2025007 Mins Read
    Share Facebook Twitter Pinterest Copy Link LinkedIn Tumblr Email Telegram WhatsApp
    Follow Us
    Google News Flipboard
    Implementing Task timeout with Swift Concurrency – Donny Wals
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link


    Swift Concurrency provides us with loads of cool and interesting capabilities. For example, Structured Concurrency allows us to write a hierarchy of tasks that always ensures all child tasks are completed before the parent task can complete. We also have features like cooperative cancellation in Swift Concurrency which means that whenever we want to cancel a task, that task must proactively check for cancellation, and exit when needed.

    One API that Swift Concurrency doesn’t provide out of the box is an API to have tasks that timeout when they take too long. More generally speaking, we don’t have an API that allows us to “race” two or more tasks.

    In this post, I’d like to explore how we can implement a feature like this using Swift’s Task Group. If you’re looking for a full-blown implementation of timeouts in Swift Concurrency, I’ve found this package to handle it well, and in a way that covers most (if not all edge cases).

    Racing two tasks with a Task Group

    At the core of implementing a timeout mechanism is the ability to race two tasks:

    1. A task with the work you’re looking to perform
    2. A task that handles the timeout

    whichever task completes first is the task that dictates the outcome of our operation. If the task with the work completes first, we return the result of that work. If the task with the timeout completes first, then we might throw an error or return some default value.

    We could also say that we don’t implement a timeout but we implement a race mechanism where we either take data from one source or the other, whichever one comes back fastest.

    We could abstract this into a function that has a signature that looks a little bit like this:

    func race(
      _ lhs: sending @escaping () async throws -> T,
      _ rhs: sending @escaping () async throws -> T
    ) async throws -> T {
      // ...
    }

    Our race function take two asynchronous closures that are sending which means that these closures closely mimic the API provided by, for example, Task and TaskGroup. To learn more about sending, you can read my post where I compare sending and @Sendable.

    The implementation of our race method can be relatively straightforward:

    func race(
      _ lhs: sending @escaping () async throws -> T,
      _ rhs: sending @escaping () async throws -> T
    ) async throws -> T {
      return try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask { try await lhs() }
        group.addTask { try await rhs() }
    
        defer { group.cancelAll() }
    
        return try await group.next()!
      }
    }

    We’re creating a TaskGroup and add both closures to it. This means that both closures will start making progress as soon as possible (usually immediately). Then, I wrote return try await group.next()!. This line will wait for the next result in our group. In other words, the first task to complete (either by returning something or throwing an error) is the task that “wins”.

    The other task, the one that’s still running, will be marked as cancelled and we ignore its result.

    There are some caveats around cancellation that I’ll get to in a moment. First, I’d like to show you how we can use this race function to implement a timeout.

    Implementing timeout

    Using our race function to implement a timeout means that we should pass two closures to race that do the following:

    1. One closure should perform our work (for example load a URL)
    2. The other closure should throw an error after a specified amount of time

    We’ll define our own TimeoutError for the second closure:

    enum TimeoutError: Error {
      case timeout
    }

    Next, we can call race as follows:

    let result = try await race({ () -> String in
      let url = URL(string: "
      let (data, _) = try await URLSession.shared.data(from: url)
      return String(data: data, encoding: .utf8)!
    }, {
      try await Task.sleep(for: .seconds(0.3))
      throw TimeoutError.timeout
    })
    
    print(result)

    In this case, we either load content from the web, or we throw a TimeoutError after 0.3 seconds.

    This approach to implementing a timeout doesn’t look very nice. We can define another function to wrap up our timeout pattern, and we can improve our Task.sleep by setting a deadline instead of duration. A deadline will ensure that our task never sleeps longer than we intended.

    The key difference here is that if our timeout task starts running “late”, it will still sleep for 0.3 seconds which means it might take a but longer than 0.3 second for the timeout to hit. When we specify a deadline, we will make sure that the timeout hits 0.3 seconds from now, which means the task might effectively sleep a bit shorter than 0.3 seconds if it started late.

    It’s a subtle difference, but it’s one worth pointing out.

    Let’s wrap our call to race and update our timeout logic:

    func performWithTimeout(
      of timeout: Duration,
      _ work: sending @escaping () async throws -> T
    ) async throws -> T {
      return try await race(work, {
        try await Task.sleep(until: .now + timeout)
        throw TimeoutError.timeout
      })
    }

    We’re now using Task.sleep(until:) to make sure we set a deadline for our timeout.

    Running the same operation as before now looks as follows:

    let result = try await performWithTimeout(of: .seconds(0.5)) {
      let url = URL(string: "
      let (data, _) = try await URLSession.shared.data(from: url)
      return String(data: data, encoding: .utf8)!
    }

    It’s a little bit nicer this way since we don’t have to pass two closures anymore.

    There’s one last thing to take into account here, and that’s cancellation.

    Respecting cancellation

    Taks cancellation in Swift Concurrency is cooperative. This means that any task that gets cancelled must “accept” that cancellation by actively checking for cancellation, and then exiting early when cancellation has occured.

    At the same time, TaskGroup leverages Structured Concurrency. This means that a TaskGroup cannot return until all of its child tasks have completed.

    When we reach a timeout scenario in the code above, we make the closure that runs our timeout an error. In our race function, the TaskGroup receives this error on try await group.next() line. This means that the we want to throw an error from our TaskGroup closure which signals that our work is done. However, we can’t do this until the other task has also ended.

    As soon as we want our error to be thrown, the group cancels all its child tasks. Built in methods like URLSession‘s data and Task.sleep respect cancellation and exit early. However, let’s say you’ve already loaded data from the network and the CPU is crunching a huge amount of JSON, that process will not be aborted automatically. This could mean that even though your work timed out, you won’t receive a timeout until after your heavy processing has completed.

    And at that point you might have still waited for a long time, and you’re throwing out the result of that slow work. That would be pretty wasteful.

    When you’re implementing timeout behavior, you’ll want to be aware of this. And if you’re performing expensive processing in a loop, you might want to sprinkle some calls to try Task.checkCancellation() throughout your loop:

    for item in veryLongList {
      await process(item)
      // stop doing the work if we're cancelled
      try Task.checkCancellation()
    }
    
    // no point in checking here, the work is already done...

    Note that adding a check after the work is already done and just before you return results doesn’t really do much. You’ve already paid the price and you might as well use the results.

    In Summary

    Swift Concurrency comes with a lot of built-in mechanisms but it’s missing a timeout or task racing API.

    In this post, we implemented a simple race function that we then used to implement a timeout mechanism. You saw how we can use Task.sleep to set a deadline for when our timeout should occur, and how we can use a task group to race two tasks.

    We ended this post with a brief overview of task cancellation, and how not handling cancellation can lead to a less effective timeout mechanism. Cooperative cancellation is great but, in my opinion, it makes implementing features like task racing and timeouts a lot harder due to the guarantees made by Structured Concurrency.



    Source link

    Concurrency Donny Implementing Swift Task timeout Wals
    Follow on Google News Follow on Flipboard
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    tonirufai
    big tee tech hub
    • Website

    Related Posts

    ios – Apple mapkit route function dose not works in China

    October 12, 2025

    swift – Does UIDevice.current.identifierForVendor change after iCloud backup and restore on another iOS device?

    October 11, 2025

    uitabbarcontroller – How to add custom UIView to floating UITabBarItem in iOS 26 Liquid Glass UITabBar

    October 10, 2025
    Add A Comment
    Leave A Reply Cancel Reply

    Editors Picks

    How to run RAG projects for better data analytics results

    October 13, 2025

    MacBook Air deal: Save 10% Apple’s slim M4 notebook

    October 13, 2025

    Part 1 – Energy as the Ultimate Bottleneck

    October 13, 2025

    From Static Products to Dynamic Systems

    October 13, 2025
    Advertisement
    About Us
    About Us

    Welcome To big tee tech hub. Big tee tech hub is a Professional seo tools Platform. Here we will provide you only interesting content, which you will like very much. We’re dedicated to providing you the best of seo tools, with a focus on dependability and tools. We’re working to turn our passion for seo tools into a booming online website. We hope you enjoy our seo tools as much as we enjoy offering them to you.

    Don't Miss!

    How to run RAG projects for better data analytics results

    October 13, 2025

    MacBook Air deal: Save 10% Apple’s slim M4 notebook

    October 13, 2025

    Subscribe to Updates

    Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

      • About Us
      • Contact Us
      • Disclaimer
      • Privacy Policy
      • Terms and Conditions
      © 2025 bigteetechhub.All Right Reserved

      Type above and press Enter to search. Press Esc to cancel.