objective-c - thread - performselectorinbackground vs dispatch_async




비동기 적으로 전달 된 블록이 완료 될 때까지 어떻게 대기합니까? (8)

Timeout 루프가 도움이되는 경우도 있습니다. 비동기 콜백 메서드에서 일부 (BOOL 일 수 있음) 신호를 얻을 때까지 기다릴 수는 있지만 응답이 없으면 그 루프에서 벗어나고 싶습니까? 여기 아래에 주로 답을했지만 시간 초과가 추가 된 해결책이 있습니다.

#define CONNECTION_TIMEOUT_SECONDS      10.0
#define CONNECTION_CHECK_INTERVAL       1

NSTimer * timer;
BOOL timeout;

CCSensorRead * sensorRead ;

- (void)testSensorReadConnection
{
    [self startTimeoutTimer];

    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {

        /* Either you get some signal from async callback or timeout, whichever occurs first will break the loop */
        if (sensorRead.isConnected || timeout)
            dispatch_semaphore_signal(sema);

        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                 beforeDate:[NSDate dateWithTimeIntervalSinceNow:CONNECTION_CHECK_INTERVAL]];

    };

    [self stopTimeoutTimer];

    if (timeout)
        NSLog(@"No Sensor device found in %f seconds", CONNECTION_TIMEOUT_SECONDS);

}

-(void) startTimeoutTimer {

    timeout = NO;

    [timer invalidate];
    timer = [NSTimer timerWithTimeInterval:CONNECTION_TIMEOUT_SECONDS target:self selector:@selector(connectionTimeout) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

-(void) stopTimeoutTimer {
    [timer invalidate];
    timer = nil;
}

-(void) connectionTimeout {
    timeout = YES;

    [self stopTimeoutTimer];
}

Grand Central Dispatch를 사용하여 비동기 처리를 수행하는 코드를 테스트하고 있습니다. 테스트 코드는 다음과 같습니다.

[object runSomeLongOperationAndDo:^{
    STAssert…
}];

테스트가 완료 될 때까지 기다려야합니다. 내 현재 솔루션은 다음과 같습니다.

__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
    STAssert…
    finished = YES;
}];
while (!finished);

어느 쪽이 조금 원유 보입니다, 당신은 더 나은 방법을 알고 있습니까? 큐를 노출 한 다음 dispatch_sync 를 호출 dispatch_sync 차단할 수 있습니다.

[object runSomeLongOperationAndDo:^{
    STAssert…
}];
dispatch_sync(object.queue, ^{});

...하지만 그건 아마도 object 에 너무 많이 노출 될 것입니다.


Xcode 6에서 XCTest를 사용하여 XCTestExpectation을 통해 비동기 테스트를 수행 할 수있는 세마포어 기술 외에도 다른 답변도 있습니다. 따라서 비동기 코드를 테스트 할 때 세마포어가 필요하지 않습니다. 예 :

- (void)testDataTask
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];

    NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
    NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        XCTAssertNil(error, @"dataTaskWithURL error %@", error);

        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
            XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
        }

        XCTAssert(data, @"data nil");

        // do additional tests on the contents of the `data` object here, if you want

        // when all done, Fulfill the expectation

        [expectation fulfill];
    }];
    [task resume];

    [self waitForExpectationsWithTimeout:10.0 handler:nil];
}

장래의 독자를 위해서, 디스패치 세마포어 기술은 절대적으로 필요한 훌륭한 기술이지만, 나는 비동기식 프로그래밍 패턴에 익숙하지 않은 너무 많은 새로운 개발자가 비동기식으로 만드는 일반적인 메커니즘으로서 너무 빨리 세마포어에 몰입한다는 것을 고백해야한다. 루틴은 동 기적으로 작동합니다. 나보다 많은 사람들이 메인 큐에서이 세마포어 기법을 사용하는 것을 보았습니다. (그리고 우리는 프로덕션 애플리케이션에서 메인 큐를 차단해서는 안됩니다.)

이 질문이 게시되었을 때 XCTestExpectation 과 같은 멋진 도구가 없었으며 이러한 테스트 스위트에서는 비동기 호출이 완료 될 때까지 테스트가 완료되지 않도록해야합니다. 이것은 주 스레드를 차단하기위한 세마포어 기술이 필요할 수있는 드문 상황 중 하나입니다.

따라서 세마포어 기술이 정상적인 원래의 질문을 작성한 저자에게이 사과를 보내면이 세마포어 기술을 사용하는 모든 새로운 개발자에게이 경고를 작성하고 비동기 처리를위한 일반적인 접근 방식으로 코드에 적용하는 것을 고려합니다. 메소드 : 세 개 중 세 번, 세마포어 기법은 비동기 작업이 발생할 때 가장 좋은 방법이 아닙니다 . 대신 완성 된 차단 / 폐쇄 패턴은 물론 위임자 프로토콜 패턴 및 알림을 익히십시오. 이것은 종종 비동기 작업을 처리하는 훨씬 더 좋은 방법입니다. 세마포어를 사용하여 동기식으로 작동하도록하는 것이 아닙니다. 대개 비동기 작업이 비동기 적으로 동작하도록 설계된 좋은 이유가 있으므로 동기식으로 동작시키지 않고 올바른 비동기 패턴을 사용하십시오.


다음은 세마포어를 사용하지 않는 멋진 트릭입니다.

dispatch_queue_t serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ, ^
{
    [object doSomething];
});
dispatch_sync(serialQ, ^{ });

당신이 할 일은 A-Synchronous 블록이 완료 될 때까지 직렬 디스패치 대기열에서 동기 적으로 대기하기 위해 빈 블록과 함께 dispatch_sync 를 사용하여 대기하는 것입니다.


문제에 대한 매우 원시적 인 해결책 :

void (^nextOperationAfterLongOperationBlock)(void) = ^{

};

[object runSomeLongOperationAndDo:^{
    STAssert…
    nextOperationAfterLongOperationBlock();
}];

최근에이 문제에 다시 와서 NSObject 다음 카테고리를 작성했습니다.

@implementation NSObject (Testing)

- (void) performSelector: (SEL) selector
    withBlockingCallback: (dispatch_block_t) block
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self performSelector:selector withObject:^{
        if (block) block();
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_release(semaphore);
}

@end

이 방법을 사용하면 비동기 호출을 테스트에서 동기식 호출로 쉽게 돌릴 수 있습니다.

[testedObject performSelector:@selector(longAsyncOpWithCallback:)
    withBlockingCallback:^{
    STAssert…
}];

SenTestingKitAsync 를 사용하면 다음과 같은 코드를 작성할 수 있습니다.

- (void)testAdditionAsync {
    [Calculator add:2 to:2 block^(int result) {
        STAssertEquals(result, 4, nil);
        STSuccess();
    }];
    STFailAfter(2.0, @"Timeout");
}

(자세한 내용은 objc.io 기사 를 참조하십시오.) 그리고 Xcode 6에는 XCTest 에 다음과 같은 코드를 작성할 수있는 AsynchronousTesting 범주가 있습니다.

XCTestExpectation *somethingHappened = [self expectationWithDescription:@"something happened"];
[testedObject doSomethigAsyncWithCompletion:^(BOOL succeeded, NSError *error) {
    [somethingHappened fulfill];
}];
[self waitForExpectationsWithTimeout:1 handler:NULL];

일반적으로 이러한 답변을 사용하지 마십시오. 종종 확장되지 않습니다 (여기 저기에 예외가 있습니다).

이러한 접근 방식은 GCD의 작동 방식과 호환되지 않으며 교착 상태가 발생하거나 논스톱 폴링으로 배터리가 소모 될 수 있습니다.

즉, 결과를 기다리는 동기가 없도록 대신 코드를 재정렬하십시오. 대신 상태 변경 (예 : 콜백 / 위임 프로토콜 사용 가능, 사용 중지, 오류 등)을 통보받는 결과를 처리하십시오. (콜백 지옥이 마음에 들지 않으면 블록으로 리팩토링 될 수 있습니다.) 앱의 나머지 부분에 실제 동작을 표시하는 방법은 잘못된 외관 뒤에 숨기는 것입니다.

대신 NSNotificationCenter 사용하여 클래스에 대한 콜백을 사용하여 사용자 정의 위임 프로토콜을 정의하십시오. 그리고 델리게이트 콜백을 사용하는 것을 마음에 들지 않으면 커스텀 프로토콜을 구현하는 구체적인 프록시 클래스로 랩핑하고 다양한 블록을 속성에 저장합니다. 아마도 편의 생성자도 제공 할 것입니다.

초기 작업은 조금 더 많았지 만 장기적으로 경주 여건과 배터리 살인 투표 수를 줄여줍니다.

(사소한 것이기 때문에 예제를 묻지 말고 객관적인 기초를 배우기 위해 시간을 투자해야했습니다.)


- (void)performAndWait:(void (^)(dispatch_semaphore_t semaphore))perform;
{
  NSParameterAssert(perform);
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  perform(semaphore);
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  dispatch_release(semaphore);
}

사용 예 :

[self performAndWait:^(dispatch_semaphore_t semaphore) {
  [self someLongOperationWithSuccess:^{
    dispatch_semaphore_signal(semaphore);
  }];
}];




grand-central-dispatch