AsyncPublisher and Sendable

As far as I can tell AsyncPublisher and AsyncThrowingPublisher are not Sendable. I'm trying to address the Sendable warnings in our code when compiling with strict-concurrency.

The aforementioned types do not seem to be Sendable, but it seems to me they will be often used in context's that require them to be Sendable.

For example, I'm capturing a reference to an AsyncPublisher in a Task closure, and that closure requires the captures to be Sendable.

What are my options in this case?

1 Like

Hey @tourultimate , did you find the workaround for this? I'm currently doing what you're doing now and wondering how did you approach this.

IIRC, I don't think we were able to fix that warning. The region based isolation might eventually help.

1 Like

I got the same issue here. Passing an AsyncPublisher, which is commonly produced by .values of @Published array, into a function that iterates the values within a task triggers the warning of non-sendable type.

I don't know if there is an elegant solution to this common situation.

For anyone looking for a (temporary) solution:

@preconcurrency import Combine

Hi! What will @preconcurrency tag do? It probably will suppress warnings, but will it make the code safe? As I understand, Sendable requires object to be actor-safe.

So if I have a class like this:

@preconcurrency import Combine

final class MyClass: Sendable {
    static let shared = MyClass()

    let publisher: AnyPublisher
}

Will it crash my app or not? When I access it from different context.

Yes @preconcurrency is a workaround to disable sendability checking for entire frameworks.

We are using publisher.values (which creates an AsyncSequence) quite a bit, but after checking, I noticed that we never pass this across isolation boundaries. I don't see AsyncThrowingPublisher conforming to Sendable in the docs, so that probably wouldn't work either.

There are certainly workarounds for your situation, but it depends on the constraints and requirements you have.

My easiest workaround would be prefixing MyClass with @MainActor, however, it's much more interesting to implement Sendable so shared instance would not produce any warning and the class would be usable from different contexts.

One approach would be to to abstract away the Publisher by wrapping it in an AsyncSequence. Plenty of approaches exist, depending on the requirements. E.g. if there's only ever one subscriber, then an AsyncStream suffices, and it offers buffering for free. For situations with multiple subscribers, we have MutableSharedStream in our codebase, which I'm unfortunately not at liberty to share.

Or, with some plumbing, you could make Shared from swift-sharing [1] work (adjustments needed if you want to mutate synchronously anywhere), which retains the current value. The idea is to start a Task that writes into Shared (or MutableSharedStream), and manage the Task's lifetime accordingly.

1: GitHub - pointfreeco/swift-sharing: A universal solution to persistence and data sharing in surprisingly little code.

Seems irrelevant. You misconcept the idea of Combine mixing it with AsyncStream.

You think probably I need Combine because I want some data to be delivered asynchronously? It doesn't follows from my example. No, I want Combine because I want its features, including multiple subscribers and all that transformations.

Looks like you correctly understood that I'm speaking about Concurrency problems caused by shared. All static let properties required to be Sendable. And to make it Sendable we need all stored properties to be Sendable, including the publisher.
Using third party lib for a singleton seems to be a completely overkill for me.

Sorry man, initially you was very helpfull but the last post value dropped. Did you write it with LLM? If so, don't waist your time, I also use LLM, many of them.

Also, if I needed just a single subscriber, I would simply use delegate.

AsyncSequence in principle supports "all that transformations", and multiple subscribers needs some workarounds that I've described. My posts are not LLM generated FWIW, but I feel you don't appreciate my answers so - hope you find a solution that suits you.