How to dispatch on main queue synchronously without a deadlock?
Asked Answered
C

3

65

I need to dispatch a block on the main queue, synchronously. I don’t know if I’m currently running on the main thread or no. The naive solution looks like this:

dispatch_sync(dispatch_get_main_queue(), block);

But if I’m currently inside of a block running on the main queue, this call creates a deadlock. (The synchronous dispatch waits for the block to finish, but the block does not even start running, since we are waiting for the current one to finish.)

The obvious next step is to check for the current queue:

if (dispatch_get_current_queue() == dispatch_get_main_queue()) {
    block();
} else {
    dispatch_sync(dispatch_get_main_queue(), block);
}

This works, but it’s ugly. Before I at least hide it behind some custom function, isn’t there a better solution for this problem? I stress that I can’t afford to dispatch the block asynchronously – the app is in a situation where the asynchronously dispatched block would get executed “too late”.

Charlot answered 26/4, 2012 at 9:28 Comment(2)
I think it is rather good solution. All you can do is make it as predefined macro, so your code will look not so ugly.Bulimia
This is more or less the "text book" solution.Mammal
P
69

I need to use something like this fairly regularly within my Mac and iOS applications, so I use the following helper function (originally described in this answer):

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

which you call via

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

This is pretty much the process you describe above, and I've talked to several other developers who have independently crafted something like this for themselves.

I used [NSThread isMainThread] instead of checking dispatch_get_current_queue(), because the caveats section for that function once warned against using this for identity testing and the call was deprecated in iOS 6.

Preventive answered 26/4, 2012 at 21:14 Comment(8)
Great answer, also dispatch_get_current_queue() is deprecated nowMirianmirielle
@Brad_Larson is there ever a genuine situation where a developer in control of their code would not know whether they are branching from the main thread or not? My belief is that if one knows how their code is working then they will know what was/is/will be on on the main thread or otherwise.Outandout
@Outandout - Yes, there are many cases where this can occur. If you have a common method in which operations are performed that must run on the main thread, yet which is called from many places, some on the main thread, some not, a function like this is extremely useful. I wrote this because I needed it in multiple places myself.Preventive
@BradLarson sounds like a case. But, if we were meant to code like that, wouldn't UIKit methods like addToSubview automatically do such a check hence removing the need for us to get the main thread manually or performSelectorOnMainThread? I believe wherever this common method is called is the same place where the the developer should call the main thread (if on a background thread that is, which the developer should be aware of). Just an opinion here.Outandout
@Outandout - This isn't just limited to UIKit or AppKit, or even the main thread. You might want to guarantee execution of specific code on a serial queue of your own that you use to wrap access to a shared resource. I do this all the time for accessing OpenGL (ES) contexts from a non-main serial queue, as just one example, and OpenGL is not designed to handle that itself. I have a serial IO library that can only be accessed on the main thread, but which might be called on within a non-main thread. It's not just UIKit and AppKit that this is helpful for.Preventive
@BradLarson of course, I was just sticking to the main thread and UIKit as an example. My first comment is general, applying to any thread as you are saying and whatever block of code. I will look into this when I get the opportunity, thank you for your time BradOutandout
How can I implement this in swift?Friarbird
Keep in mind this opinion against isMainThreadFalsify
G
2

For syncing on the main queue or on the main thread (that is not the same) I use:

import Foundation

private let mainQueueKey    = UnsafeMutablePointer<Void>.alloc(1)
private let mainQueueValue  = UnsafeMutablePointer<Void>.alloc(1)


public func dispatch_sync_on_main_queue(block: () -> Void)
{
    struct dispatchonce  { static var token : dispatch_once_t = 0  }
    dispatch_once(&dispatchonce.token,
    {
        dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueValue, nil)
    })

    if dispatch_get_specific(mainQueueKey) == mainQueueValue
    {
        block()
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(),block)
    }
}

extension NSThread
{
    public class func runBlockOnMainThread(block: () -> Void )
    {
        if NSThread.isMainThread()
        {
            block()
        }
        else
        {
            dispatch_sync(dispatch_get_main_queue(),block)
        }
    }

    public class func runBlockOnMainQueue(block: () -> Void)
    {
        dispatch_sync_on_main_queue(block)
    }
}
Garrotte answered 29/6, 2016 at 13:24 Comment(0)
M
0

I recently began experiencing a deadlock during UI updates. That lead me this Stack Overflow question, which lead to me implementing a runOnMainQueueWithoutDeadlocking-type helper function based on the accepted answer.

The real issue, though, is that when updating the UI from a block I had mistakenly used dispatch_sync rather than dispatch_async to get the Main queue for UI updates. Easy to do with code completion, and perhaps hard to notice after the fact.

So, for others reading this question: if synchronous execution is not required, simply using dispatch_**a**sync will avoid the deadlock you may be intermittently hitting.

Metallophone answered 28/3, 2016 at 17:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.