Swift Concurrency Resources:
DevForums tags: Concurrency
The Swift Programming Language Concurrency > Concurrency documentation
WWDC 2022 Session 110351 Eliminate data races using Swift Concurrency — This ‘sailing on the sea of concurrency’ talk is a great introduction to the fundamentals.
WWDC 2021 Session 10134 Explore structured concurrency in Swift — The table that starts rolling out at around 25:45 is really helpful.
Dispatch Resources:
DevForums tags: Dispatch
Dispatch documentation — Note that the Swift API and C API, while generally aligned, are different in many details. Make sure you select the right language at the top of the page.
Dispatch man pages — While the standard Dispatch documentation is good, you can still find some great tidbits in the man pages. See Reading UNIX Manual Pages. Start by reading dispatch in section 3.
WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD [1]
WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage [1]
Avoid Dispatch Global Concurrent Queues DevForums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] These videos may or may not be available from Apple. If not, the URL should help you locate other sources of this info.
Concurrency
RSS for tagConcurrency is the notion of multiple things happening at the same time.
Posts under Concurrency tag
102 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Let's say I have a Task that I want to extend into the background with beginBackgroundTask(expirationHandler:). Furthermore, I'd like to leverage cooperative cancelation of subtasks when responding to the expiration handler. Unfortunately, the expirationHandler: closure parameter is not async, so I'm unable to do something like:
actor MyTaskManagerOne {
var backgroundID = UIBackgroundTaskIdentifier.invalid
func start() {
Task {
let doTheWorkTask = Task {
await self.doTheWork()
}
backgroundID = await UIApplication.shared.beginBackgroundTask {
doTheWorkTask.cancel()
// next line: compile error, since not an async context
await doTheWorkTask.value // ensure work finishes up
// next line: generates MainActor compilation warnings despite docs allowing it
UIApplication.shared.endBackgroundTask(self.backgroundID)
}
await doTheWorkTask.value
}
}
func doTheWork() async {}
}
So instead, I think I have to do something like this. It, however, generates runtime warnings, since I'm not directly calling endBackgroundTask(_:) at the end of the expirationHandler:
actor MyTaskManagerTwo {
var backgroundID = UIBackgroundTaskIdentifier.invalid
func start() {
Task {
let doTheWorkTask = Task {
await self.doTheWork()
}
backgroundID = await UIApplication.shared.beginBackgroundTask {
doTheWorkTask.cancel()
// 1. not calling endBackgroundTask here generates runtime warnings
}
await doTheWorkTask.value
// 2. even though endBackgroundTask gets called
// here (as long as my cooperative cancellation
// implementations abort quickly in `doTheWork()`)
await UIApplication.shared.endBackgroundTask(self.backgroundID)
}
}
func doTheWork() async {}
}
As best I can tell, the MyTaskManagerTwo actor works and does not cause a watchdog termination (as long as cancellation is sufficiently fast). It is, however, producing the following runtime warning:
Background task still not ended after expiration handlers were called: <_UIBackgroundTaskInfo: 0x302753840>: taskID = 2, taskName = Called by libswift_Concurrency.dylib, from <redacted>, creationTime = 9674 (elapsed = 28). This app will likely be terminated by the system. Call UIApplication.endBackgroundTask(_:) to avoid this.
Is the runtime warning ok to ignore in this case?
AVAudioFormat has no Swift concurrency annotations but the documentation states "Instances of this class are immutable."
This made me always assume it was safe to pass AVAudioFormat instances around. Is this the case? If so can it be marked as Sendable? Am I missing something?
Usage of multiple async lets crashes the app in a nondeterministic fashion. We are experiencing this crash in production, but it is rare.
0 libswift_Concurrency.dylib 0x20a8b89b4 swift_task_create_commonImpl(unsigned long, swift::TaskOptionRecord*, swift::TargetMetadata<swift::InProcess> const*, void (swift::AsyncContext* swift_async_context) swiftasynccall*, void*, unsigned long) + 384
1 libswift_Concurrency.dylib 0x20a8b6970 swift_asyncLet_begin + 36
We managed to isolate the issue, and we submitted a technical incident (Case-ID: 8007727). However, we were completely ignored, and referred to the developer forums.
To reproduce the bug you need to run the code on a physical device and under instruments (we used swift concurrency). This bug is present on iOS 17 and 18, Xcode 15.1, 15.4 and 16 beta, swift 5 and 6, including strict concurrency.
Here's the code for Swift 6 / Xcode 16 / strict concurrency:
(I wanted to attach the project but for some reason I am unable to)
typealias VoidHandler = () -> Void
enum Fetching { case inProgress, idle }
protocol PersonProviding: Sendable {
func getPerson() async throws -> Person
}
actor PersonProvider: PersonProviding {
func getPerson() async throws -> Person {
async let first = getFirstName()
async let last = getLastName()
async let age = getAge()
async let role = getRole()
return try await Person(firstName: first,
lastName: last,
age: age,
familyMemberRole: role)
}
private func getFirstName() async throws -> String {
try await Task.sleep(nanoseconds: 1_000_000_000)
return ["John", "Kate", "Alex"].randomElement()!
}
private func getLastName() async throws -> String {
try await Task.sleep(nanoseconds: 1_400_000_000)
return ["Kowalski", "McMurphy", "Grimm"].randomElement()!
}
private func getAge() async throws -> Int {
try await Task.sleep(nanoseconds: 2_100_000_000)
return [56, 24, 11].randomElement()!
}
private func getRole() async throws -> Person.Role {
try await Task.sleep(nanoseconds: 500_000_000)
return Person.Role.allCases.randomElement()!
}
}
@MainActor
final class ViewModel {
private let provider: PersonProviding = PersonProvider()
private var fetchingTask: Task<Void, Never>?
let onFetchingChanged: (Fetching) -> Void
let onPersonFetched: (Person) -> Void
init(onFetchingChanged: @escaping (Fetching) -> Void,
onPersonFetched: @escaping (Person) -> Void) {
self.onFetchingChanged = onFetchingChanged
self.onPersonFetched = onPersonFetched
}
func fetchData() {
fetchingTask?.cancel()
fetchingTask = Task {
do {
onFetchingChanged(.inProgress)
let person = try await provider.getPerson()
guard !Task.isCancelled else { return }
onPersonFetched(person)
onFetchingChanged(.idle)
} catch {
print(error)
}
}
}
}
struct Person {
enum Role: String, CaseIterable { case mum, dad, brother, sister }
let firstName: String
let lastName: String
let age: Int
let familyMemberRole: Role
init(firstName: String, lastName: String, age: Int, familyMemberRole: Person.Role) {
self.firstName = firstName
self.lastName = lastName
self.age = age
self.familyMemberRole = familyMemberRole
}
}
import UIKit
class ViewController: UIViewController {
@IBOutlet private var first: UILabel!
@IBOutlet private var last: UILabel!
@IBOutlet private var age: UILabel!
@IBOutlet private var role: UILabel!
@IBOutlet private var spinner: UIActivityIndicatorView!
private lazy var viewModel = ViewModel(onFetchingChanged: { [weak self] state in
switch state {
case .idle:
self?.spinner.stopAnimating()
case .inProgress:
self?.spinner.startAnimating()
}
}, onPersonFetched: { [weak self] person in
guard let self else { return }
first.text = person.firstName
last.text = person.lastName
age.text = "\(person.age)"
role.text = person.familyMemberRole.rawValue
})
@IBAction private func onTap() {
viewModel.fetchData()
}
}
Good day.
I'm having problems debugging hangs. Watched the video WWDC Analyze hangs with Instruments. Apparently the main thread is blocked. It’s just not possible to analyze what exactly is blocking the flow. Please help me figure it out.
https://drive.google.com/file/d/1QvFK4y79CG0fBMrJPOrBCTuWsOaqichY/view?usp=share_link <- trace
I'm continuing with the migration towards Swift 6. Within one of our libraries, I want to check whether a parameter object: Any? confirms to Sendable.
I tried the most obvious one:
if let sendable = object as? Sendable {
}
But that results into the compiler error "Marker protocol 'Sendable' cannot be used in a conditional cast".
Is there an other way to do this?
I have the following code:
extension AssetGridViewController: PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
Task { @MainActor in
guard let changes = changeInstance.changeDetails(for: fetchResult) else { return }
fetchResult = changes.fetchResultAfterChanges
}
}
}
With Swift 6, this generates a compilation error: Main actor-isolated instance method 'photoLibraryDidChange' cannot be used to satisfy nonisolated protocol requirement. The error includes to fix-it suggestions:
Adding nonisolated to the function (nonisolated func photoLibraryDidChange(_ changeInstance: PHChange))
Adding @preconcurrency to the protocol conformance (extension AssetGridViewController: @preconcurrency PHPhotoLibraryChangeObserver {)
Both options generate a runtime error: EXC_BREAKPOINT (code=1, subcode=0x105b7c400). For context, AssetGridViewController is a regular UIViewController.
Any ideas on how to fix this?
Hello!
In our codebase we have a NSView subclass that conforms to NSTextInputClient. This protocol is currently not marked as a main actor, but in the decade this has been in use here it has always been called on the main thread from AppKit.
With Swift 6 (or complete concurrency checking in Swift 5) this conformance causes issues since NSView is a main actor but not this protocol. I've tried a few of the usual fixes (MainActor.assumeIsolated or prefixing the protocol conformance with @preconcurrency) but they were not able to resolve all warnings.
So I dug in the AppKit headers and found that NSTextInputClient is usually implemented by the view itself, but that that is not a hard requirement (see NSTextInputContext.h the documentation for the client property or here). With that I turned my NSView subclass extension into a separate class that is not a main actor and in my NSView subclass create an instance of it and NSTextInputContext. This all seems to work fine in my initial tests, the delegate methods are called. But when the window loses and then regains key, I see a warning message in the console output.
-[TUINSCursorUIController activate:]: Foo.TextInputClient isn't subclass of NSView.
So my question is, am I doing it wrong with the custom class that implements the protocol? Or is the warning wrong?
I would also appreciate a hint on how to better resolve the concurrency issues with NSTextInputClient. Is a main actor annotation coming at some point from your end?
Thanks!
Markus
The AppleArchive module is pretty cool, but it relies almost entirely on two stream types, ArchiveByteStream and ArchiveStream, which don't really work well in a Swift Concurrency-based workflow since they all use thread-blocking mechanisms like pthread mutexes for synchronization, and threads in the cooperative pool should not be blocked. They also use their own thread pools for processing, independently of the cooperative thread pool, making it easy to end up with more threads than one has cores. (Perhaps even more so if you try to compress/decompress multiple files at once? The documentation isn't clear on whether separate archive operations share the same thread pool or not, but since it allows you to choose the size of the pool, it seems that these may be separate from each other as well.)
Are there any plans for an interface to Apple Archive that would fit better with the structured concurrency model?
With the integration of Apple's pushToTalk framework - we create the PTChannelManager using its async initializer from AppDidFinishLaunching - using an actor to ensure the PTChannelManager is only created once.
With this we have been seeing a lot of crashes for users in our analytics dashboards happening about ~2 seconds after app launch around a task-dealloc.
Here is a simplified version of our actor and Manager - where the manager just shows the init. The init of it is an async optional init because the creation of the PTChannelManager uses an async throws.
actor PushToTalkDeviceContainer {
private var internalPushToTalkManagerTask: Task<PushToTalkManager?, Never>?
func pushToTalkManager() async -> PushToTalkManager? {
#if !os(visionOS)
if let internalPushToTalkManagerTask {
return await internalPushToTalkManagerTask.value
}
let internalPushToTalkManagerTask = Task<PushToTalkManager?, Never> {
return await PushToTalkManagerImp()
}
self.internalPushToTalkManagerTask = internalPushToTalkManagerTask
return await internalPushToTalkManagerTask.value
#else
return nil
#endif
}
}
public class PushToTalkManagerImp: PushToTalkManager {
public let onPushToTalkDelegationEvent: AnyPublisher<PushToTalkDelegationEvent, Never>
public let onPushToTalkAudioSessionChange: AnyPublisher<PushToTalkManagerAudioSessionChange, Never>
public let onChannelRestoration: AnyPublisher<UUID, Never>
private let ptChannelManager: PTChannelManager
private let restorationDelegate: PushToTalkRestorationDelegate
private let delegate: PushToTalkDelegate
init?() async {
self.delegate = PushToTalkDelegate()
self.restorationDelegate = PushToTalkRestorationDelegate()
self.onPushToTalkDelegationEvent = delegate.pushToTalkDelegationSubject.eraseToAnyPublisher()
self.onPushToTalkAudioSessionChange = delegate.audioSessionSubject.eraseToAnyPublisher()
self.onChannelRestoration = restorationDelegate.restorationDelegateSubject.eraseToAnyPublisher()
do {
ptChannelManager = try await PTChannelManager.channelManager(delegate: delegate, restorationDelegate: restorationDelegate)
} catch {
return nil
}
}
}
The crash stack trace is as follows:
0 libsystem_kernel.dylib 0x00000001e903342c __pthread_kill + 8 (:-1)
1 libsystem_pthread.dylib 0x00000001fcdd2c0c pthread_kill + 268 (pthread.c:1721)
2 libsystem_c.dylib 0x00000001a7ed6c34 __abort + 136 (abort.c:159)
3 libsystem_c.dylib 0x00000001a7ed6bac abort + 192 (abort.c:126)
4 libswift_Concurrency.dylib 0x00000001ab2bf7c8 swift::swift_Concurrency_fatalErrorv(unsigned int, char const*, char*) + 32 (Error.cpp:25)
5 libswift_Concurrency.dylib 0x00000001ab2bf7e8 swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 32 (Error.cpp:35)
6 libswift_Concurrency.dylib 0x00000001ab2c39a8 swift_task_dealloc + 128 (TaskAlloc.cpp:59)
7 MyApp 0x0000000104908e04 PushToTalkManagerImp.__allocating_init() + 40 (PushToTalkManager.swift:0)
8 MyApp 0x0000000104908e04 closure #1 in PushToTalkDeviceContainer.pushToTalkManager() + 60
9 MyApp 0x00000001041882e9 specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 (<compiler-generated>:0)
10 MyApp 0x0000000103a652bd partial apply for specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 (<compiler-generated>:0)
11 libswift_Concurrency.dylib 0x00000001ab2c2775 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:463)
if we define a property in the following way:
@property (atomic) NSString *latestObject;
Can we assume that the read write to that value is thread safe?
i.e the value will be correct.
Or it is better to write our own setter/getter with Locks?
In an iOS viewController, I use the NSDiffableDataSource to populate a tableView from the results of a CoreData fetchController. The result class is defined by CoreData - in this case a class named CDFilterStack.
In xCode 16.0 Beta with strict concurrency checking = 'Complete' the CDFilterStack class has this warning everywhere it is referenced.
"Type 'CDFilterStack' does not conform to the 'Sendable' protocol; this is an error in the Swift 6 language mode."
The class definition of CDFilterStack is not editable because it is generated by CoreData.
I think I need to mark this class (and other similar classes) as @preconcurrency... but how & where?
Here's one method sample that generates three of these warnings
func initialSnapShot() -> NSDiffableDataSourceSnapshot<Int, CDFilterStack> {
var snapshot = NSDiffableDataSourceSnapshot<Int, CDFilterStack>()
if let sections = dataProvider.fetchedResultsController.sections {
for index in 0..<sections.count
{
let thisSection = sections[index]
guard let sectionStacks = thisSection.objects as? [CDFilterStack]
else { continue}
snapshot.appendSections([index])
snapshot.appendItems(sectionStacks)
} // for loop that will continue on error in objects
}
return snapshot
}
Careful reading of the various migration guides hasn't produced an example of how to handle this.. (btw the migration guides are nicely done:)
I have a background thread that is updating a swift data model Item using a ModelActor. The background thread runs processing an Item and updates the Item's status field. I notice that if I have a view like
struct ItemListView: View {
@Query private var items: [Items]
var body: some View {
VStack {
ForEach(items) { item in
ItemDetailView(item)
}
}
}
}
struct ItemDetailView: View {
var item: Item
var body: some View {
// expected: item.status automatically updates when the background thread updates the `Item`'s `status`.
Text(item.status)
// actual: This text never changes
}
}
Then background updates to the Item's status in SwiftData does not reflect in the ItemDetailView. However, if I inline ItemDetailView in ItemListView like this:
struct ItemListView: View {
@Query private var items: [Items]
var body: some View {
VStack {
ForEach(items) { item in
// Put the contents of ItemDetailView directly in ItemListView
Text(item.status)
// result: item.status correctly updates when the background thread updates the item.
}
}
}
}
Then the item's status text updates in the UI as expected. I suspect ItemDetailView does not properly update the UI because it just takes an Item as an input. ItemDetailView would need additional understanding of SwiftData, such as a ModelContext.
Is there a way I can use ItemDetailView to show the Item's status and have the UI show the status as updated in the background thread?
In case details about my background thread helps solve the problem, my thread is invoked from another view's controller like
@Observable
class ItemCreateController {
func queueProcessingTask() {
Task {
let itemActor = ItemActor(modelContainer: modelContainer)
await itemActor.setItem(item)
await itemActor.process()
}
}
}
@ModelActor
actor ItemActor {
var item: Item?
func setItem(_ item: Item) {
self.item = modelContext.model(for: item.id) as? Item
}
func process() async {
// task that runs processing on the Item and updates the Item's status as it goes.
}
Sorry this post is duplicate
I was looking to the example code that point from What's new in SwiftData session and noticed something that I cannot understand about Swift concurrency. from this snippet:
static func createSampleData(into modelContext: ModelContext) {
Task { @MainActor in
let sampleDataTrips: [Trip] = Trip.previewTrips
let sampleDataLA: [LivingAccommodation] = LivingAccommodation.preview
let sampleDataBLT: [BucketListItem] = BucketListItem.previewBLTs
let sampleData: [any PersistentModel] = sampleDataTrips + sampleDataLA + sampleDataBLT
sampleData.forEach {
modelContext.insert($0)
}
if let firstTrip = sampleDataTrips.first,
let firstLivingAccommodation = sampleDataLA.first,
let firstBucketListItem = sampleDataBLT.first {
firstTrip.livingAccommodation = firstLivingAccommodation
firstTrip.bucketList.append(firstBucketListItem)
}
if let lastTrip = sampleDataTrips.last,
let lastBucketListItem = sampleDataBLT.last {
lastTrip.bucketList.append(lastBucketListItem)
}
try? modelContext.save()
}
}
From the code snippet, I cannot see any task that needs to be marked with await, and it is also marked as @MainActor.
My question is: why do we need to put Task here? It seems like all the code will run on the main thread anyway. I am just afraid that I might be missing some concept of Swift concurrency or SwiftData here.
Thank you
Hey,
I am just about to prepare my app for Swift 6, and facing the issue that UserDefaults is not Sendable. The documentation states that its thread safe, so I am wondering, why is it not marked as Sendable? Was it just forgotten? Is it safe to mark it as nonisolated(unsafe) or @unchecked Sendable?
I have a project with some legacy networking code that uses the Stream (formerly NSStream) family of classes, including Stream, InputStream, OutputStream, and StreamDelegate.
None of these are sendable, so I get a lot of warnings when implementing delegate methods in a @MainActor class.
These classes seem like they could be sendable. Is this something that will happen soon? Is it a bug I should report?
The networking code that uses these classes runs great, and hasn't needed changes for years, so my current solution is to just mark these unchecked:
extension Stream: @unchecked Sendable { }
extension InputStream: @unchecked Sendable { }
extension OutputStream: @unchecked Sendable { }
This makes the compiler happy, but makes me feel kind of bad. Is there something else I could do?
I'm trying to convert my project to use Swift 6 with Complete Concurrency in Xcode 16 beta 1.
The project uses TipKit, but I'm getting compile errors when trying to use the TipKit Parameters feature. Here is an example of the type of error I'm seeing (Note that this code from https://developer.apple.com/documentation/tipkit/highlightingappfeatureswithtipkit):
struct ParameterRuleTip: Tip {
// Define the app state you want to track.
@Parameter
static var isLoggedIn: Bool = false
Static property '$isLoggedIn' is not concurrency-safe because it is non-isolated global shared mutable state.
Is there a new pattern for supporting TipKit Parameters in Swift 6 with Complete Concurrency enabled? There is no obvious suggestion for how to fix this.
The latest WWDC 2024 TipKit doesn't appear to have any solution(s).
I suspect this will be a "wait for the next beta" item, but thought I'd throw it out here in case anyone knows of a workaround.
Mac app compiling under Xcode 16 beta 1. Trying to get rid of all warning that would stop the adoption of Swift 6 -- Strict Concurrency Checking is set to Complete.
I'm down to one warning before I can enable swift 6.
SwiftUI.Commands
Main actor-isolated static method '_makeCommands(content:inputs:)' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode
That's because I've added menu commands to the app. It's very easy to reproduce.
import SwiftUI
@main
struct CommandApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
HelpCommand()
}
}
}
struct HelpCommand: Commands {
var body: some Commands {
CommandGroup(replacing: .help) {
Button("Help me") {
//
}
}
}
}
The suggested fix is telling me what change I should make ot _makeCommands. At least that is how I'm reading it.
Hi,
When using Swift Concurrency blocking tasks like file I/O, GPU work and networking can prevent forward moving progress and have the potential to exhaust the cooperative thread pool and under utilize the CPU. It's been recommended to offload these tasks from the cooperative thread pool.
Is my understanding correct that the preferred way to do this is by creating async tasks via Dispatch or OperationQueue? And combining these with Continuations if a return value from the task is required? Or should I always be using Continuations in combination with Dispatch/OperationQueue?
There are also Executors but the documentation seems a bit limited on how to use these. The new TaskExecutor is also only available on the latest beta's.
My question is basically what is the recommend way to offload a task?
Thanks!
Hi,
Introducing Swift Concurrency to my Metal app has been a bit challenging as Swift Concurrency is limited by the cooperative thread pool.
GPU work is obviously not CPU bound and can block forward moving progress, especially when using waitUntilCompleted on the command buffer. For concurrent render work this has the potential of under utilizing the CPU and even creating dead locks.
My question is, what is the Metal's teams general recommendation when it comes to concurrency? It seems to me that Dispatch or OperationQueues are still the preferred way for Metal bound tasks in order to gain maximum performance?
To integrate with Swift Concurrency my idea is to use continuations that kick off render jobs via Dispatch or Queues? Would this be the best solution to bridge async tasks with Metal work?
Thanks!