7

내가 궁금한 점은 cancel/cancelAllOperations/.isCancelled를 GCD로 시작한 스레드와 함께 사용할 수 있습니까?cancel/isCancelled를 GCD/dispatch_async와 함께 사용할 수 있습니까?

현재 백그라운드 프로세스를 취소하기 위해 플래그로 부울을 사용합니다.

취소 버튼을 잡을 수 있도록 (또는 프로세서가 작동 중임을 나타 내기 위해 무언가를 움직일 수 있도록) UI 반응성을 유지하면서 백그라운드에서 많은 처리를하고 싶다고 가정 해 보겠습니다. 여기에 ...이 모두가 완벽하게 작동

#define CHECKER if (pleaseAbandonYourEfforts == YES) \ 
{NSLog(@"Amazing Interruption System Working!");return;} 

를 ... 우리가 그것을 할 방법

@interface AstoundingView : UIView 
    { 
    BOOL pleaseAbandonYourEfforts; 
    blah 
    } 
@implementation AstoundingView 
// 
// these are the foreground routines... 
// begin, abandon and all-done 
// 
-(void)userHasClickedToBuildASpaceship 
    { 
    [YourUIStateMachine buildShip]; 
    [self procedurallyBuildEnormousSpaceship]; 
    } 
-(void)userHasClickedToAbandonBuildingTheSpaceship 
    { 
    [YourUIStateMachine inbetween]; 
    pleaseAbandonYourEfforts = false; // that's it! 
    } 
-(void)attentionBGIsAllDone 
    { 
// you get here when the process finishes, whether by completion 
// or if we have asked it to cancel itself. 
    [self typically setNeedsDisplay, etc]; 
    [YourUIStateMachine nothinghappening]; 
    } 
// 
// these are the background routines... 
// the kickoff, the wrapper, and the guts 
// 
// The wrapper MUST contain a "we've finished" message to home 
// The guts can contain messages to home (eg, progress messages) 
// 
-(void)procedurallyBuildEnormousSpaceship 
    { 
    // user has clicked button to build new spaceship 
    pleaseAbandonYourEfforts = FALSE; 
    dispatch_async(
     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), 
     ^{ [self actuallyProcedurallyBuildInBackground]; } 
     ); 

    // as an aside, it's worth noting that this does not work if you 
    // use the main Q rather than a global Q as shown. 
    // Thus, this would not work: 
    // dispatch_async(dispatch_get_main_queue(), ^{ ...; }); 
    } 

-(void)actuallyProcedurallyBuildInBackground 
    { 
    // we are actually in the BG here... 

    [self setUpHere]; 

    // set up any variables, contexts etc you need right here 
    // DO NOT open any variables, contexts etc in "buildGuts" 

    // when you return back here after buildGuts, CLEAN UP those 
    // variables, contexts etc at this level. 

    // (using this system, you can nest as deep as you want, and the 
    // one CHECKER pseudocall will always take you right out. 
    // You can insert CHECKERs anywhere you want.) 

    [self buildGuts]; 

    // Note that any time 'CHECKER' "goes off', you must fall- 
    // through to exactly here. This is the common fall-through point. 
    // So we must now tidy-up, to match setUpHere. 

    [self wrapUpHere]; 

    // when you get to here, we have finished (or, the user has cancelled 
    // the background operation) 

    // Whatever technique you use, 
    // MAKE SURE you clean up all your variables/contexts/etc before 
    // abandoning the BG process. 

    // and then you must do this...... 

    // we have finished. it's critical to let the foreground know NOW, 
    // or else it will sit there for about 4 to 6 seconds (on 4.3/4.2) 
    // doing nothing until it realises you are done 

    dispatch_sync(
     dispatch_get_main_queue(), 
     ^{[self attentionBGIsAllDone];} // would setneedsdisplay, etc 
     ); 

    return; 
    } 
-(void)buildGuts 
    { 
    // we are actually in the BG here... 

    // Don't open any local variables in here. 

    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 

    // to get stuff done from time to time on the UI, something like... 

    CHECKER 
    dispatch_sync(
     dispatch_get_main_queue(), 
     ^{[supportStuff pleasePostMidwayImage: 
      [UIImage imageWithCGImage:halfOfShip] ];} 
     ); 

    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    for (i = 1 to 10^9) 
     { 
     CHECKER 
     [self blah blah]; 
     } 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 

    return; 
    } 

및 CHECKER는 플래그가 true를 확인할 것보다 아무것도 더하지 않습니다.

하지만 ........ GCD 사용과 관련하여 cancel/cancelAllOperations/.isCancelled를 사용할 수 있습니까?

여기에 이야기가 있습니까? 건배.


PS -이 "여섯 부분"배경 템플릿을 사용하는 모든 초보자 용.

주 BJ는 아래의 강조로, 당신은 BG 과정에서 휴식 할 때마다 ... 그

당신이 열려있는 모든 변수를 정리해야합니다!

나의 관용구에서는 "setUpHere"에 모든 변수, 컨텍스트, 메모리 등을 할당해야합니다. 그리고 당신은 "wrapUpHere"에서 그들을 공개해야합니다. (이 관용구는 BG에있는 동안 깊고 깊게 간다면 계속 작동합니다.)

또는 BJ가 보여준 것과 정확하게 일치시켜야합니다. (BJ의 방법을 사용하는 경우 더 깊게 들어가면 조심하십시오.)

사용하는 방법은 BG 프로세스를 벗어날 때 열려있는 변수/컨텍스트/메모리를 정리해야합니다. 누군가가 도움이되기를 바랍니다.

+0

나는 당신이 단일 복귀 지점에 대한 나의 요점을 놓쳤다 고 생각합니다. 'buildGuts' 메쏘드에 임시 객체를 할당했다면 메쏘드를 종료하고 그 객체에 대한 포인터를 잃기 전에 그 메쏘드에서 메쏘드를 해제해야합니다. –

+1

단일 반환 지점에 대한 내 진술을 명확히하는 또 다른 업데이트를 추가했습니다. 당신의'self는 빠르게 활을 감싼다.'call *은 필요한 모든 메모리 관리를 처리 할 수없고,'buildGuts' 메소드가 그것을 수정하지 않은 후에 특정 메소드가 실행되도록 보장 할 수 없다. 하나의 리턴 포인트를 언급 할 때, 메소드가 종료 될 수있는 유일한 장소가 있음을 의미했습니다. 구현시, 그것은'CHECKER' 매크로가 사용되는 곳이면 어디에서나 빠져 나올 수 있습니다. 그것은 단일 반환 지점이 아닙니다. –

+0

안녕하세요. BJ, 코드 예제에서 건배를 설명하는 몇 가지 설명을 추가했습니다. – Fattie

답변

18

GCD에는 취소 지원 기능이 기본적으로 내장되어 있지 않습니다. 백그라운드 작업을 취소 할 수있는 것이 중요하다면 시연했던 것처럼 플래그를 확인하는 것이 허용 가능한 솔루션입니다. 그러나 취소가 응답해야하는 속도를 평가할 수도 있습니다. 이러한 메소드 호출 중 일부가 매우 짧으면 점검 빈도를 줄일 수 있습니다.

취소를 지원하기 위해 NSOperation 플래그를 사용할 수 있는지 여부를 묻는 메시지가 표시됩니다. 내 대답은 아니오 야. GCD는 NSOperation을 기반으로하지 않습니다. 사실 Snow Leopard NSOperation과 NSOperationQueue는 내부적으로 GCD를 사용하기 위해 다시 구현되었습니다. 그래서 의존성은 다른 방향입니다. NSOperation은 GCD보다 상위 레벨 구조입니다. 비록 당신이 NSOperation을 사용했다 할지라도 당신의 취소 구현은 거의 동일 할 것입니다; self.isCancelled을 주기적으로 점검해야만 우주선 건설을 포기해야하는지 확인할 수 있습니다.

CHECKER 매크로를 구현할 때 유일한 염려는 예기치 않은 return을 구현한다는 것입니다. 따라서 메모리 누수에주의해야합니다. 백그라운드 스레드에서 NSAutoreleasePool을 설정했다면 돌아 오기 전에 drain해야합니다.alloc ed 또는 retain 개체가있는 경우 다시 돌아 오기 전에 release해야 할 수 있습니다.

모든 점검에서 모든 정리 작업이 필요하므로 단일 리턴 지점으로 이동하는 것이 좋습니다. 이 작업을 수행하는 한 가지 방법은 각 메서드 호출을 if (pleaseAbandonYourEfforts == NO) { } 블록으로 래핑하는 것입니다. 이렇게하면 취소가 요청되면 메소드의 끝까지 빠르게 빠져 나가고 단일 위치에서 정리를 유지할 수 있습니다. 또 다른 옵션은 매크로를 사용하여 goto cleanup;을 호출하고 해제해야하는 항목을 해제하는 메서드 끝 부분에 cleanup: 레이블을 정의하는 것입니다. 어떤 사람들은 거의 종교적인 방식으로 goto을 사용하는 것을 싫어하지만, 이와 같은 정리 레이블로 건너 뛰는 것이 종종 대안보다 더 깔끔한 해결책이라는 것을 알았습니다. 당신이 그것을 좋아하지 않는다면, if 블럭의 모든 것을 랩하는 것만으로도 효과가 있습니다.


편집 본인은 단일 반환 지점을하는 것에 대한 내 이전 문을 명확히 할 필요성을 느낍니다. 위에 정의 된대로 CHECKER 매크로를 사용하면 -buildGuts 메서드는 해당 매크로가 사용되는 지점에서 반환 할 수 있습니다. 해당 메소드에 대해 로컬로 유지 된 오브젝트가있는 경우 리턴하기 전에 정리해야합니다. CHECKER 매크로 방법이 끝나기 전에 돌아가 우리를 발생하는 경우이 경우, 다음 formatter의 개체 수 없음을

-(void)buildGuts 
{ 
    // we are actually in the BG here... 

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 

    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self recordSerialNumberUsingFormatter:formatter]; 

    // ... etc ... 

    [formatter release]; 

    return; 
} 

참고 : 예를 들어, -buildGuts 방법이 매우 합리적인 수정을 상상 해제 및 유출됩니다. [self quickly wrap up in a bow] 호출은 인스턴스 변수 또는 전역 포인터를 통해 도달 할 수있는 모든 개체에 대한 정리를 처리 할 수 ​​있지만 buildGuts 메서드에서만 로컬로 사용할 수있는 개체를 해제 할 수는 없습니다.

#define CHECKER if (pleaseAbandonYourEfforts == YES) { goto cleanup; } 

-(void)buildGuts 
{ 
    // we are actually in the BG here... 

    NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 

    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self blah blah]; 
    CHECKER 
    [self recordSerialNumberUsingFormatter:formatter]; 

    // ... etc ... 

cleanup: 
    [formatter release]; 

    return; 
} 

이 구현에서, formatter는 관계없이 항상 취소가 발생했을 때의 발매 예정 : 이것은 내가 같이 것 goto cleanup 구현을 제안하는 이유입니다.

간단히 말해서 메서드에서 돌아올 수있는 매크로를 만들 때마다 중간에 반환하기 전에 모든 메모리 관리가 처리되었는지 확인해야합니다. 그것은 반환을 일으키는 매크로로 깨끗하게하기가 어렵습니다.

+2

안녕하세요. BJ - GCD에 대한 훌륭한 답변과 NSOperation/etc와의 관계에 감사드립니다. 환상적 !! 다시 말하면 ** GCD에는 취소 지원 **이 없습니다. 그렇다면 진짜 관용어가 없다면 자기 자신에게만하는 것이 가장 좋습니다. 최고 감사합니다. – Fattie

+0

안녕하세요. BJ .. 당신이 위로 깰 때 모든 지역 변수를 정리하는 것에 관해서 : 물론. 나는 이디엄을 설명하기 위해 코드 예제 안에 더 많은 주석을 추가했다. – Fattie

1

감사합니다. 필자는 아직 완료되지 않은 이전의 요청을 취소하는 새로운 비동기 요청을 허용하고 싶었습니다. 위 예제에서, 새로운 요청을 내기 전에 처리되지 않은 요청이 취소되었다는 신호를 attentionBGIsAllDone 콜백을 통해 기다려야합니다. 대신에, 나는 뛰어난 요청에 연결할 수있는 인스턴스있는 간단한 부울 래퍼 생성 :

@interface MyMutableBool : NSObject { 
    BOOL value; 
} 
@property BOOL value; 
@end 

@implementation MyMutableBool 
@synthesize value; 
@end 

을 그리고 pleaseAbandonYourEfforts에 대한 그의 인스턴스를 사용합니다. 내가 전에 내 dispatch_async은 (즉, 위의 procedurallyBuildEnormousSpaceship에서) 나는 이전 요청을 취소하고 다음과 같이 새로운 준비 : 비동기 작업을 수행

// First cancel any old outstanding request. 
cancelRequest.value = YES; 
// Now create a new flag to signal whether or not to cancel the new request. 
MyMutableBool *cancelThisRequest = [[[MyMutableBool alloc] init] autorelease]; 
self.cancelRequest = cancelThisRequest; 

내 블록은 물론 cancelThisRequest.value을 확인해야합니다.

관련 문제