Sending to TaskGroup child task

I have been looking into sending parameters from SE-0430. By marking a function parameter as sending I can pass it into a Task

class NonSendable {
    init() {}
}

func sendToTask(_ t: sending NonSendable) {
    Task {
        print(t)
    }
}

But I cannot do the same for a child task of TaskGroup

func sendToChildTask(_ t: sending NonSendable) async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            print(t)
        }
    }
}

The above code produces the error Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure

Is there a reason I can't do this with child tasks?

It appears to be related to the closure provided to withTaskGroup because I can create an instance of NonSendable inside that closure and then able to pass it into the child task.

3 Likes

You might find useful information in Sending, inout sending, Mutex in which I recently asked similar questions and got helpful answers!

I think in this case, it's because the compiler doesn't know how many times the closure passed to withTaskGroup will be called — if withTaskGroup called it twice, it would alias your NonSendable value.

1 Like

Looks like this was already reported. Wrong error when passing `sending` closure to `TaskGroup` · Issue #76242 · swiftlang/swift · GitHub

I think it is because the closure provided to withTaskGroup is not tagged sending.

You can confirm this doesn't help by wrapping up withTaskGroup:

open class NonSendable {}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func withTaskGroup2<ChildTaskResult, GroupResult>(
    of childTaskResultType: ChildTaskResult.Type,
    returning returnType: GroupResult.Type = GroupResult.self,
    isolation: isolated (any Actor)? = #isolation,
    body: sending (inout TaskGroup<ChildTaskResult>) async -> GroupResult
) async -> GroupResult where ChildTaskResult : Sendable {
    // no warning:
    await withTaskGroup(of: childTaskResultType, returning: returnType, isolation: isolation, body: body)
}

func sendToChildTask(_ t: sending NonSendable) async {
    await withTaskGroup2(of: Void.self) { group in
        // same warning:
        group.addTask {
            print(t)
        }
    }
}