AwaitKit icon indicating copy to clipboard operation
AwaitKit copied to clipboard

Return to main thread

Open Mijail opened this issue 6 years ago • 4 comments

Hello!

Maybe this is a stupid question but I haven't been able to solve it as of yet.

I have something like

async {
    let report = try await(self.reportStore.getReport())
    //update UI
}

As I am inside the async I am not in the main thread and get whacky behaviour on the UI. But if I call the await in the main thread I get a deadlock on the semaphore wait. what should I do if I want to get back to the main thread with my result?

Thanks

Mijail avatar Mar 01 '18 10:03 Mijail

Yes indeed, this is a bit annoying. For the moment the workaround is to use the DispatchQueue.main.async method.

yannickl avatar Mar 03 '18 22:03 yannickl

I was trying to think of a work-around for being able to call await from the main thread without locking a semaphore.

This is what I came up with:


extension DispatchQueue {
    @discardableResult
    public func awaitOnMainQueue<T>(_ promise: Promise<T>, interval: TimeInterval = 0.1) throws -> T {
        /// Checking for main thread does NOT really guarantee the main queue, but what else can we do?
        guard Thread.isMainThread else {
            throw NSError(domain: "com.yannickloriot.awaitkit", code: 0, userInfo: [
                NSLocalizedDescriptionKey: "Operation was aborted.",
                NSLocalizedFailureReasonErrorKey: "This method can only be run on the main queue."
                ])
        }

        var result: T?
        var error: Swift.Error?
        var completed: Bool = false
        
        promise
            .then(on: self) { value -> Promise<Void> in
                result = value
                completed = true
                return Promise()
            }
            .catch(on: self, policy: .allErrors) { err in
                error = err
                completed = true
        }
        
        while !completed {
            RunLoop.current.run(until: NSDate().addingTimeInterval(interval) as Date)
        }
        
        guard let unwrappedResult = result else {
            throw error!
        }
        
        return unwrappedResult
    }
}

public func awaitOnMainQueue<T>(_ promise: Promise<T>, interval: TimeInterval = 0.1) throws -> T {
    return try DispatchQueue.main.awaitOnMainQueue(promise, interval: interval)
}

The reason I needed this is that many network APIs dispatch their results back to the main queue.

flockoffiles avatar Jun 01 '18 14:06 flockoffiles

@flockoffiles But why does your solution result in a deadlock when calling mainQueueAwait() inside a DispatchQueue()? I have a Dispatch Group with a notify() method and when I call mainQueueAwait() inside, it deadlocks.

teodorciuraru avatar Aug 01 '18 15:08 teodorciuraru

you can put your UI updating code in a done { } block after async because async will return Promise<Void>. The done block is automatically called on the main thread.

djtech42 avatar Feb 25 '19 20:02 djtech42