Free giveaway: Win a ticket for AppDevCon. Learn more.
Free: Win a ticket for AppDevCon.
Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Task.sleep() vs. Task.yield(): The differences explained

In Swift Concurrency, we can use Task.sleep() and Task.yield() to let a specific task sleep or yield for a period of time. Both look and behave similarly, but there are a few significant differences to be aware of as Swift developers.

Knowing these differences allows you to better understand when to use which and how you can optimize your code accordingly. Before diving into the differences, let’s first explain what these modifiers do.

How to use Task.sleep()

Using Task.sleep() you can suspend a task for a given duration:

try await Task.sleep(for: .seconds(5))

Unlike regular sleep methods we know from non-concurrency code, this sleep does not block the underlying thread. This means it permits lower-priority tasks that were awaiting to run.

If the task is canceled before the sleep ends, this function will throw a CancellationError, which we know from regular task cancellation.

When should I use Task.sleep()?

You should use a sleep when you want to introduce a delay in an asynchronous context. A common example is to debounce user input and wait for a pause in typing before executing a search request:

func search(_ query: String) {
    /// Cancel any previous searches that might be 'sleeping'.
    currentSearchTask?.cancel()
    
    currentSearchTask = Task {
        do {
            /// Sleep for 0.5 seconds to wait for a pause in typing before executing the search.
            try await Task.sleep(for: .milliseconds(500))
            
            print("Starting to search!")
            
            /// A simplified static result and search implementation.
            searchResults = Self.articleTitlesDatabase
                .filter { $0.lowercased().contains(query.lowercased()) }
        } catch {
            print("Search was cancelled!")
        }
    }
}

The full code example with SwiftUI implementation will be available in my Swift Concurrency Course. Join the waitlist.

Other common usecases are:

  • Polling an API at intervals in combination with async sequences
  • Rate limiting outgoing network requests
  • Artificial delays for UI testing

Make sure to consider cancellation when using Task.sleep() by respecting the throwing functionality.

Join the waitlist: Swift Concurrency Course

I’m launching a new course on Swift Concurrency soon! Join the waitlist to be the first to know when it drops and unlock an exclusive early-bird discount:

How to use Task.yield()

The Task.yield() method suspends the current task and allows other tasks to execute:

await Task.yield()

You can use the yield method to suspend a task, potentially during a long-running operation. This allows other tasks to run for a while before execution returns to this task.

Note, though: if the current task is already the highest-priority task in the system, the executor will immediately resume executing the same task. In other words, the Task.yield() method might not have any impact.

When should I use Task.yield()?

In my experience, there aren’t many common use cases in which you’ll need to use Task.yield(). In most cases, it’s better to use Task.sleep(). For me, the most common use case has been when I write tests for asynchronous code. I’m a big fan of the Swift Concurrency Extras repository, which demonstrates usage as follows:

func testIsLoading() async {
    /// Run on a serial executor to attempt running all tasks serially.
    await withMainSerialExecutor {
        let model = NumberFactModel(getFact: {
            /// Yield the current task and let other tasks continue first.
            await Task.yield()
            return "\($0) is a good number."
        })

        let task = Task { await model.getFactButtonTapped() }
        
        /// Yield the test so the `getFactButtonTapped()` method gets called.
        await Task.yield()
        XCTAssertEqual(model.isLoading, true)
        XCTAssertEqual(model.fact, nil)

        /// Wait for the task to return its value.
        await task.value
        XCTAssertEqual(model.isLoading, false)
        XCTAssertEqual(model.fact, "0 is a good number.")
    }
}

Using Task.yield() here allows you to let your code under test progress and ensure the results are consistently available for test validation. If you have another example, I’d love to know!

The differences between Task.sleep() and Task.yield()

Both methods suspend execution, but they have a few key differences.

The Task.sleep() method suspends execution for a set of time while the Task.yield() method might only suspend if other tasks with similar or lower priority await execution. Therefore, the duration of suspension is only fixed for Task.sleep() and indeterminate for Task.yield().

The sleep method is interruptible via cancellation, while Task.yield() only yields control. Both are non-blocking for their respective threads.

Conclusion

Both Task.sleep() and Task.yield() suspend execution but do it differently. In most cases, you’ll be using the sleep method, while the yield method can be useful when writing tests for asynchronous methods.

If you’re keen to learn more about Swift Concurrency, make sure to check out my course or read any of my other Swift Concurrency articles.

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.