Combine: How to block on Future.sink

I have an API that returns futures. They are always resolved from a background thread. I have a few places where I need to call this API from the main thread and block the main thread until the future resolves.

I don't see any API for this... is this possible? Or if not can you suggest best approach to implement?

Thanks,
Jesse

This seems to do what I want for my particular use case:

extension Future {
    public func syncSink(receiveCompletion: @escaping ((Subscribers.Completion<Failure>) -> Void), receiveValue: @escaping ((Output) -> Void)) {
        let semaphore = DispatchSemaphore(value: 0)
        var completion: Subscribers.Completion<Failure>?
        var output: Output?

        let _ = self.subscribe(on: DispatchQueue.global(qos: .background)).sink(
            receiveCompletion: { innerCompletion in
                completion = innerCompletion
                semaphore.signal()
            }) { innerOutput in
                output = innerOutput
            }
        
        semaphore.wait()
        
        if let output = output {
            receiveValue(output)
        }
        
        receiveCompletion(completion!)
    }
}

I still wonder if it can be improved or if there's a better approach.

Thanks,
Jesse

You really, really shouldn't do this. Not only is Combine designed to be an async framework, so any synchronous handling is working against the intent of the library, but you should never, ever block the main queue. The main queue handles all user input and updates the UI, so you're blocking the user from interacting with anything, and if you block long enough, you run the risk of being killed by the system watch dog (on non-macOS OSes). A better approach here would be to rearchitect to accept the async nature of the APIs you're consuming.

1 Like

Thanks for your help.

I understand it's not a great pattern, and generally I'm using combine without needing it. But there are a few cases that I'm unsure how to avoid.

For example I have a NSDocument subclass and need to override write(to:ofType:). The file writing needs to happen somehow within that method. But my document model access API is all async and future based. So when implementing write(to:ofType:) in my NSDocument subclass I need to:

  1. self.model.getData() -> Future<Data, Never>
  2. somehow wait for that future to resolve
  3. write the data

Thanks,
Jesse

I've never used NSDocument, but reading its documentation there do seem to be APIs for indicating your write method is asynchronous, so I would suggest investigating how you're supposed to implement that functionality. At the very least, it seems that you can use those APIs to indicate your write occurs on a background thread, so I would suggest blocking that thread instead of the main queue.

If you have more questions about NSDocument, Apple's Developer Forums or StackOverflow may get you more responses.

1 Like