1

Recently I came to a point where I needed some block of code to execute always on the main thread synchronously. This block can be called from any thread. I solved this problem with the code that was already suggested in this SO answer by @Brad Larson

As the comments to this answer it is evident that the deadlock can occur, but I got into the deadlock very very easily. Please have a look at this code.

-(IBAction) buttonClicked
{
   // Dispatch on the global concurrent queue async.
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
     NSString* data =  [self getTheString];
     NSLog(@"From Background Thread: %@", data);
   };

   // Dispatch on the main queue async.
   dispatch_async(dispatch_get_main_queue(), ^{
     NSString* data = [self getTheString];
     NSLog(@"From Main Thread: %@", data);
   };
}

// This method can be called from any thread so synchronize it.
// Also the code that sets the string variable based on some logic need to execute on main thread.

-(NSString*) getTheString
{
   __block NSString* data = nil;
    @synchronized(self)
    {
        // Have some code here that need to be synchronized between threads.
        // .......
        //

        // Create a block to be executed on the main thread.

        void (^blockToBeRunOnMainThread)(void) = ^{
            // This is just a sample. 
            // Determining the actual string value can be more complex.
            data = @"Tarun";
        };

        [self dispatchOnMainThreadSynchronously:blockToBeRunOnMainThread];
    }
}

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

In this piece of code there are two simultaneous asynchronous requests to function getTheString (Assume you have no control over the buttonClicked method and how it calls getTheString api) . Suppose the request from global queue comes first and it is trying to run the block on the main thread synchronously, till that time background thread in waiting for main thread to execute the block synchronously, at the same time request from main queue comes and trying the acquire the lock from background thread, but as background thread in not complete main thread waiting for background thread to complete. Here we have a deadlock on main thread as main thread waiting for background thread to finish, and background thread is waiting for main thread to execute block.

If I remove the @synchronize statement everything works fine as expected. May be I don't need a @synchronize statement here but in same case you may need to have this. Or it can even happen from some other parts of the code.

I tried to search the whole net for the solution and also tried dispatch_semaphore but couldn't solve the issue. May be I am just not doing things the right way.

I assume this is classic problem of deadlock and faced by developers again and again, and probably have solved it to some extent. Can anyone help with this, or point me to right direction?

Community
  • 1
  • 1
tarun_sharma
  • 761
  • 5
  • 22
  • The usual answer is: don't do that. Writing code that is triggered from the main thread and calls back to the main thread synchronously is always asking for exactly this problem. You're basically asking for a deadlock. – Avi Nov 18 '15 at 08:24

1 Answers1

1

I would create a synchronous queue (NSOperationQueue would be simplest) and submit the block to be run on the main thread to that queue. The queue would dispatch the blocks in the order received, maintaining the ordering you desire. At the same time, it disassociates the synchronicity between calling the getTheString method and the dispatch to the main thread.

Avi
  • 7,469
  • 2
  • 21
  • 22
  • Isn't using NSOperationQueue is equivalent to using dispatch_async. Off course I have better control with operation queues. For main thread I can use [NSOperationQueue mainQueue], it solves the ordering problem but that again is not synchronous (I need this to return a value real time). I can make it synchronous by sending waitUntilAllOperationsAreFinished (there is only one operation added) but that again equivalent to using dispatch_sync, right? – tarun_sharma Nov 18 '15 at 10:45
  • In that case, you should change the `getTheString` method to take a completion block, to which it will pass the string. This way you can remain asynchronous. What you're trying to do just won't work. – Avi Nov 18 '15 at 12:06