5

Tôi đã cố gắng hiểu lý do đằng sau vụ tai nạn này để hiểu thêm về cách hoạt động của các khối. Tôi có một lớp học thực sự đơn giản để kích hoạt vụ tai nạn này.Tai nạn với dispatch_block

@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 

Bây giờ, nếu tôi bố trí các lớp và chỉ cần gọi phương thức doSomething với nó,

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

Nó bị treo với ngoại lệ, EXC_BAD_INSTRUCTION và chồng sau dấu vết,

#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() 

Nếu tôi sửa đổi phương thức doSomething, như vậy mà nó không sử dụng yếu nhưng sử dụng tự sau đó vụ tai nạn không xảy ra và các phương pháp dường như thực hiện như mong đợi,

- (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); 
} 

Tại sao nó sụp đổ, sự hiểu biết của tôi là sử dụng yếu bên trong khối sẽ chắc chắn rằng phương pháp sẽ không được gọi, nếu đối tượng được phát hành và tôi nghĩ rằng yếu là an toàn hơn sử dụng tự bên trong khối.

Đoạn mã trên hoạt động tốt với weakSelf, nếu tôi giữ lại các đối tượng BlockCrashTest và gọi phương thức với nó.

Tôi sẽ thực sự hạnh phúc nếu ai đó có thể giải thích lý do đằng sau vụ tai nạn và chính xác những gì xảy ra với 3 biến thể mã khác nhau ở trên mà một sự cố và dường như hoạt động tốt.

Lưu ý: Đây là bằng cách này hay khác có liên quan đến vụ tai nạn được liệt kê trong chủ đề, Objective-C crash on __destroy_helper_block_. Tôi đã có thể tạo lại chính xác các dấu vết ngăn xếp với mã của tôi ở trên.

Trả lời

3

Một vài quan sát:

  1. Bạn không thể có một nhóm công văn với không cân bằng "enter" và "đi" khi đối tượng dispatch_group_t được deallocated. Và như ilya đã chỉ ra, vì mẫu của bạn, strongSelfnil, vì vậy bạn đang nhập nhóm, nhưng không rời khỏi.

    Một mẫu rất phổ biến trong điệu nhảy weakSelfstrongSelf là chỉ cần kiểm tra xem nếu strongSelfnil hay không, hãy giải quyết sự mất cân bằng. Vì vậy, nếu strongSelfnil, nó bỏ qua những thứ nhóm văn hoàn toàn, nhưng nếu nó không phải là nil, cả hai "enter" và "để lại" sẽ được gọi là:

    - (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); 
         } 
        }); 
    } 
    

    Rõ ràng, bạn phải chắc chắn rằng performSomethingAsync phương pháp , chính nó, luôn luôn gọi khối chặn rời khỏi nhóm.

  2. Một cách khác giải quyết này (nếu bạn không có sự đảm bảo rằng tất cả các "enter" và "rời" sẽ là cân bằng), là sử dụng Cột:

    - (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"); 
         } 
        }); 
    } 
    

    Thẳng thắn mà nói, thậm chí khi sử dụng các semaphores, như tôi đã nói ở trên, tôi vẫn nghĩ rằng người ta muốn kiểm tra để xác nhận rằng strongSelf không phải là nil. Lập trình đồng thời đủ gây nhầm lẫn mà không cần thêm khả năng của thông báo vào một đối tượng nil dẫn đến không có op.

    - (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"); 
          } 
         } 
        }); 
    } 
    
+0

Bạn cũng có thể cho tôi một đầu mối về lý do tại sao nó không sụp đổ, nếu tôi sử dụng tự bên trong khối? Có phải chỉ vì khối đó vẫn giữ nguyên bản thân? – Sandeep

+1

@GeneratorOfOne - Bởi vì sự hiện diện của 'self' thiết lập tham chiếu mạnh mẽ bên trong khối, mà không được giải quyết cho đến khi khối được thực hiện xong. Vì vậy, 'weakSelf' không phải là' nil' và 'strongSelf' cũng không phải là' nil'. Vì vậy, khối xử lý hoàn thành của bạn trong 'performSomethingAsync' đang được gọi, đảm bảo rằng" enter "bây giờ được cân bằng bởi" leave ". Nhưng nếu bạn muốn khối giữ lại đối tượng, bạn rõ ràng sẽ không trải qua điệu nhảy yếu/chính mình. – Rob

+0

Đây là giải thích tốt. Vì vậy, tôi có thể có một số đối tượng autoreleased mà vẫn có thể làm một số cuộc gọi không đồng bộ dài và vẫn còn xung quanh nếu tôi sử dụng tự, thay vì sử dụng yếu/mạnh. Trong khi nếu đối tượng là nếu giữ lại chính nó, sau đó nó có ý nghĩa để có yếu/mạnh mẽ, là ý chính của điều này? – Sandeep

2

Bạn sẽ gặp phải sự cố tương tự ngay cả khi bạn xóa cuộc gọi self performSomethingAsync:. Sự cố này gây ra bởi libdispatch API semaphore. Bạn có thể thấy dấu vết lắp ráp các chức năng bị rơi _dispatch_semaphore_dispose trong Xcode: enter image description here

Nếu chúng ta cố gắng tìm ra những gì xảy ra trong mã này, chúng ta sẽ thấy rằng bạn rõ ràng dấu hiện khối vào nhóm bằng cách gọi dispatch_group_enter. Sau đó, performSomethingAsync không gọi vì strongSelf == nil. Điều này có nghĩa là dispatch_group_enter không cân bằng với dispatch_group_leave điều này khiến nhóm không thể xử lý đúng cách và bị lỗi (xem danh sách asm).

Nếu bạn sử dụng self mã này cũng bị lỗi vì dispatch_group_leave(group); được gọi từ chủ đề khác với dispatch_group_enter Điều này cũng gây ra sự cố tương tự nhưng ở góc độ khác: cuộc gọi không được cân bằng trong cùng một chuỗi. performSomethingAsync được gọi là khối hoàn thành trong hàng đợi khác nhau không nằm trong số của bạn "com.queue.test".

Ví dụ này chỉ là sai khi sử dụng dispatch_groups API.Để xem cách sử dụng đúng cách, hãy xem apple doc.

1

hệ điều hành MacOS 10,8 và iOS 6.0 giới thiệu ARC để gửi các đối tượng. Từ tài liệu GCD Objects and Automatic Reference Counting:

Khi bạn tạo ứng dụng bằng trình Objective-C, tất cả đối tượng gửi là đối tượng mục tiêu-C. Như vậy, khi tính năng đếm tham chiếu tự động (ARC) được kích hoạt, các đối tượng gửi đi được giữ lại và tự động phát hành giống như bất kỳ đối tượng Objective-C nào khác. Khi ARC không được kích hoạt, sử dụng các hàm dispatch_retain và dispatch_release (hoặc ngữ nghĩa Objective-C) để giữ lại và giải phóng các đối tượng công văn của bạn. Bạn không thể sử dụng các chức năng giữ/giải phóng Core Foundation.

Nếu bạn cần sử dụng ngữ nghĩa lưu giữ/phát hành trong ứng dụng hỗ trợ ARC với mục tiêu triển khai sau (để duy trì tính tương thích với mã hiện có), bạn có thể tắt đối tượng gửi đối tượng dựa trên C bằng cách thêm -DOS_OBJECT_USE_OBJC = 0 vào cờ biên dịch của bạn.

Trong trường hợp ARC của bạn đang quản lý hạnh phúc vòng đời của dispatch_group_t. Và, thật không may, mã của bạn đang khiến nhóm được phát hành trong khi khóa vẫn đang chờ. Khi nhóm lần ra nó được phát hành - vì vậy khi dispatch_group_leave được gọi là nó bị treo, khi nhóm đã được phát hành.

Tôi khuyên bạn nên kiểm tra tối thiểu nhóm là NULL trước khi cố gắng rời khỏi nhóm.

Ngoài ra, logic kết quả chờ đợi của bạn được đảo ngược. Một kết quả bằng không cho biết nhóm đã được làm trống trước khi hết thời gian chờ, kết quả khác không cho biết thời gian chờ đã được nhấn.