Unexpected Task suspension points in Swift Concurrency
Article Summary
Antoine van der Lee uncovered a sneaky Swift Concurrency bug that was silently destroying his app's performance. The culprit? An innocent-looking Task that was secretly hopping on and off the main thread hundreds of times.
When tasks inherit @MainActor isolation unnecessarily, they create unexpected suspension points that block the main thread even when no UI work is happening. Van der Lee demonstrates how this pattern, common in many codebases, causes rapid thread switching that compounds when operations run repeatedly.
Key Takeaways
- Tasks inheriting @MainActor isolation suspend immediately, waiting for main thread availability
- Thread hopping occurs: tasks jump to main thread then immediately suspend again
- Multiple queued tasks create cascading delays, blocking UI updates from completing
- Fix with @concurrent annotation to start tasks on correct isolation domain
- Xcode Instruments Swift Concurrency template visualizes these hidden suspension points
A single line of code (@concurrent in) eliminated unnecessary main thread blocking and dramatically improved time to first UI result.
About This Article
Antoine van der Lee found that tasks inheriting @MainActor isolation create unnecessary suspension points. The executor has to wait for main thread availability before executing, even when no UI work happens right away.
Van der Lee added the @concurrent annotation to Task initialization. This lets tasks start on a different isolation domain immediately. When UI updates are actually needed, the code explicitly returns to the main thread using MainActor.run.
Time to first UI result improved after fixing the unnecessary suspension point. A single-line code change reduced main thread contention when operations run repeatedly in short periods.