Failure with dispatch_block

I am trying to understand the cause of this accident in order to better understand how the blocks behave. I have a really simple class to trigger this crash.

@implementation BlockCrashTest - (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); __weak typeof(self) weakSelf = self; dispatch_block_t block = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; dispatch_group_t group = dispatch_group_create(); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_group_enter(group); [strongSelf performSomethingAsync:^{ dispatch_group_leave(group); }]; if(dispatch_group_wait(group, time) != 0) { NSLog(@"group already finished"); } }; dispatch_async(queue, block); } - (void)performSomethingAsync:(void(^)(void))completion { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(5); completion(); }); } - (void)dealloc { NSLog(@"released object"); } @end 

Now, if I select a class and just call doSomething ,

 BlockCrashTest *someObject = [[BlockCrashTest alloc] init]; [someObject doSomething]; 

It crashes with the exception EXC_BAD_INSTRUCTION and monitors the stack trace,

 #0 0x000000011201119a in _dispatch_semaphore_dispose () #1 0x0000000112013076 in _dispatch_dispose () #2 0x0000000112026172 in -[OS_dispatch_object _xref_dispose] () #3 0x000000010ef4c2fd in __29-[BlockCrashTest doSomething]_block_invoke at /Users/Sandeep/Desktop/Test Block Crash/Test Block Crash/ViewController.m:35 #4 0x0000000112005ef9 in _dispatch_call_block_and_release () 

If I modify the doSomething method so that it does not use weak , but uses self, then the crash does not occur, and the methods seem to execute as expected.

 - (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); dispatch_block_t block = ^{ dispatch_group_t group = dispatch_group_create(); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_group_enter(group); [self performSomethingAsync:^{ dispatch_group_leave(group); }]; if(dispatch_group_wait(group, time) != 0) { NSLog(@"group already finished"); } }; dispatch_async(queue, block); } 

Why is this a failure, I understand that using the weak inside the block will ensure that the method will not be called if the object is released, and I thought the weak is safer than using self inside the block.

The code above works fine with weakSelf if I save the BlockCrashTest object and call its method.

I would be very happy if someone could explain the cause of the accident, and what exactly happens with these three different code variants above that one failure and the others seem to work fine.

Note. This is somehow related to the crash pointed to by the thread, Objective-C crash on __destroy_helper_block_ . I was able to reproduce the exact stack traces with my code above.

+5
source share
3 answers

A few observations:

  • You cannot have a dispatch group with unbalanced "enter" and "leave" when the dispatch_group_t object is freed. And, as Ilya pointed out, because of your picture strongSelf is nil , so you enter the group, but do not leave it.

    A very common picture in weakSelf and strongSelf is simply to check whether strongSelf nil or not, eliminating the imbalance. Thus, if strongSelf is nil , it bypasses the material of the sending group altogether, but if it is not nil , enter and leave are called:

     - (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); [strongSelf performSomethingAsync:^{ dispatch_group_leave(group); }]; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } }); } 

    Obviously, you need to make sure that the performSomethingAsync method, itself, always calls the block that leaves the group.

  • Another way to resolve this issue (if you have no assurances that all "enter" and "leave" will be balanced), you should use semaphores:

     - (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [strongSelf performSomethingAsync:^{ dispatch_semaphore_signal(semaphore); }]; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); if (dispatch_semaphore_wait(semaphore, time) != 0) { NSLog(@"semaphore not received in time"); } }); } 

    Honestly, even when using semaphores, like mine, I still think I need to check to confirm that strongSelf not nil . Concurrent programming is quite confusing, without adding the ability of a message to the nil object, which leads to the absence of an operation.

     - (void)doSomething { dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL); typeof(self) weakSelf = self; dispatch_async(queue, ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [strongSelf performSomethingAsync:^{ dispatch_semaphore_signal(semaphore); }]; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); if (dispatch_semaphore_wait(semaphore, time) != 0) { NSLog(@"semaphore not received in time"); } } }); } 
+3
source

You will get the same crash even if you delete the self performSomethingAsync: call. This crash is caused by the libdispatch semaphore API. In Xcode, you can see the failure diagram of the broken _dispatch_semaphore_dispose function: enter image description here

If we try to figure out what is going on in this code, we will see that you explicitly marked the current block entered into the group by calling dispatch_group_enter . Then performSomethingAsync does not call, because strongSelf == nil . This means that dispatch_group_enter not balanced with dispatch_group_leave , which means that the group cannot be disposed of properly and fails (see Listing asm).

If you use self , this code also crashed because dispatch_group_leave(group); called from another thread using dispatch_group_enter , which also causes the same failure, but in a different perspective: calls are not balanced in one thread. performSomethingAsync is called a termination block in another queue, not in your "com.queue.test" .

In this example, it is incorrect to use the dispatch_groups APIs. To find out how to use it properly, see apple doc .

+2
source

MacOS 10.8 and iOS 6.0 introduced ARC for sending objects. From the documentation for GCD Objects and Automatic Link Counting :

When you create an application using the Objective-C compiler, all submit objects are Objective-C objects. Thus, when automatic reference counting (ARC) is enabled, send objects are saved and freed automatically, like any other Objective-C object. When ARC is not enabled, use the dispatch_retain and dispatch_release (or Objective-C semantics) functions to save and release dispatch objects. You cannot use Core Foundation's save / release features.

If you need to use save / release semantics in an ARC-enabled application with a later deployment objective (to ensure compatibility with existing code), you can disable Objective-C send objects by adding -DOS_OBJECT_USE_OBJC = 0 to your compiler flags.

In your case, ARC happily manages the life cycle of your dispatch_group_t . And, unfortunately, your code causes the group to be published while the lock is still waiting. When a group is played, it is freed - therefore, when dispatch_group_leave is called, it fails because the group is already freed.

I would suggest that at least checking that the group is NULL before trying to leave it.

In addition, your wait result logic is canceled. A null result indicates that the group has been emptied to a timeout, a result without zero indicates that the wait time has been hit.

+1
source

Source: https://habr.com/ru/post/1232688/


All Articles