비동기식으로 디스패치된 블록이 완료될 때까지 기다리는 방법은 무엇입니까?
저는 그랜드 센트럴 디스패치를 이용하여 비동기 처리를 하는 코드를 테스트하고 있습니다.테스트 코드는 다음과 같습니다.
[object runSomeLongOperationAndDo:^{
STAssert…
}];
테스트는 작업이 완료될 때까지 기다려야 합니다.현재 솔루션은 다음과 같습니다.
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
어느 쪽이 좀 조잡해 보이는데, 더 좋은 방법이 있나요?대기열을 노출한 다음 전화를 걸어 차단할 수 있습니다.dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
…하지만 이는 아마도 너무 많이 노출되는 것일 수 있습니다.object
.
를 사용하려고 합니다.dispatch_semaphore
다음과 같이 보여야 합니다.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object runSomeLongOperationAndDo:^{
STAssert…
dispatch_semaphore_signal(sema);
}];
if (![NSThread isMainThread]) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
} else {
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
이는 다음과 같은 경우에도 올바르게 동작해야 합니다.runSomeLongOperationAndDo:
작업이 실제로 스레드화를 수행할 만큼 길지 않다고 판단하고 대신 동기식으로 실행합니다.
답변에서 다룬 하여 다른답서모에다, 제우를 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
또한 이러한 테스트 제품군에서는 비동기 호출이 완료될 때까지 테스트가 완료되지 않도록 해야 합니다.이것은 주 스레드를 차단하기 위한 세마포 기법이 필요할 수 있는 드문 상황 중 하나입니다.
그래서 세마포어 기법이 타당한 이 원래 질문의 저자에게 사과의 말씀을 드리며, 이 세마포어 기법을 보고 비동기식 방법을 다루는 일반적인 접근법으로 코드에 적용하는 것을 고려하는 모든 새로운 개발자들에게 이 경고를 씁니다: 10번 중 9번은 경고를 받았습니다.세마포어 기법은 비동기 작업을 처리할 때 최선의 방법이 아닙니다.대신, 완료 블록/폐쇄 패턴뿐만 아니라 대리자-프로토콜 패턴 및 알림을 숙지해야 합니다.이것들은 종종 세마포를 사용하여 동기적으로 동작하게 만드는 것보다 비동기 작업을 처리하는 훨씬 더 나은 방법입니다.일반적으로 비동기 작업은 비동기적으로 동작하도록 설계되었기 때문에 동기적으로 동작하도록 만들기보다는 올바른 비동기 패턴을 사용합니다.
이 를 저는최근에이다다작카다니성습했를에 썼습니다.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…
}];
일반적으로 이러한 답변을 사용하지 않고 확장할 수 없는 경우가 많습니다(예외 사항도 있음).
이러한 접근 방식은 GCD의 작동 방식과 호환되지 않으므로 교착 상태를 유발하거나 중단 없는 폴링으로 배터리를 소모시킬 수 있습니다.
즉, 결과를 동시에 기다리지 않고 상태 변경(예: 콜백/대리인 프로토콜, 사용 가능 여부, 부재중, 오류 등)을 통보받는 결과를 처리하도록 코드를 다시 정렬합니다.이것이 가짜 외관 뒤에 숨기는 것보다 실제 행동을 앱의 나머지 부분에 노출시키는 방법이기 때문입니다.
대신 NSNotificationCenter를 사용하여 클래스에 대한 콜백이 있는 사용자 지정 대리자 프로토콜을 정의합니다.그리고 대리자 콜백을 전체적으로 사용하는 것이 싫다면 사용자 지정 프로토콜을 구현하고 다양한 블록을 속성에 저장하는 구체적인 프록시 클래스로 래핑합니다.아마도 편의 시설도 제공할 것입니다.
초기 작업은 약간 더 많지만 장기적으로 끔찍한 인종 조건과 배터리 살인 투표의 수를 줄일 것입니다.
(예를 들어 묻지 마십시오. 사소한 일이기 때문에 우리는 목표-c 기초도 배우기 위해 시간을 투자해야 했습니다.)
세마포어를 사용하지 않는 멋진 묘기가 있습니다.
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ, ^
{
[object doSomething];
});
dispatch_sync(serialQ, ^{ });
은 다을사여대다니기합하를 사용하여 입니다.dispatch_sync
A-Synchronous 블록이 완료될 때까지 직렬 디스패치 대기열에서 동기적으로 대기합니다.
- (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);
}];
}];
SenTestingKitAsync도 있어 다음과 같은 코드를 작성할 수 있습니다.
- (void)testAdditionAsync {
[Calculator add:2 to:2 block^(int result) {
STAssertEquals(result, 4, nil);
STSuccess();
}];
STFailAfter(2.0, @"Timeout");
}
(자세한 내용은 objc.io 기사를 참조하십시오.)그리고 Xcode 6 이후로AsynchronousTesting
입니다.XCTest
다음과 같은 코드를 작성할 수 있습니다.
XCTestExpectation *somethingHappened = [self expectationWithDescription:@"something happened"];
[testedObject doSomethigAsyncWithCompletion:^(BOOL succeeded, NSError *error) {
[somethingHappened fulfill];
}];
[self waitForExpectationsWithTimeout:1 handler:NULL];
다음은 제 테스트 중 하나의 대안입니다.
__block BOOL success;
NSCondition *completed = NSCondition.new;
[completed lock];
STAssertNoThrow([self.client asyncSomethingWithCompletionHandler:^(id value) {
success = value != nil;
[completed lock];
[completed signal];
[completed unlock];
}], nil);
[completed waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[completed unlock];
STAssertTrue(success, nil);
스위프트 4:
사용하다synchronousRemoteObjectProxyWithErrorHandler
에 remoteObjectProxy
원격 개체를 생성할 때 사용합니다.더 이상 세마포가 필요 없습니다.
아래 예제에서는 프록시에서 받은 버전을 반환합니다.다음을 제외하고는synchronousRemoteObjectProxyWithErrorHandler
충돌합니다(액세스할 수 없는 메모리에 액세스할 수 없음).
func getVersion(xpc: NSXPCConnection) -> String
{
var version = ""
if let helper = xpc.synchronousRemoteObjectProxyWithErrorHandler({ error in NSLog(error.localizedDescription) }) as? HelperProtocol
{
helper.getVersion(reply: {
installedVersion in
print("Helper: Installed Version => \(installedVersion)")
version = installedVersion
})
}
return version
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object blockToExecute:^{
// ... your code to execute
dispatch_semaphore_signal(sema);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
이것이 저를 위해 해냈습니다.
시간 초과 루프도 유용할 수 있습니다.비동기 콜백 방식에서 일부(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];
}
문제에 대한 매우 원시적인 해결책:
void (^nextOperationAfterLongOperationBlock)(void) = ^{
};
[object runSomeLongOperationAndDo:^{
STAssert…
nextOperationAfterLongOperationBlock();
}];
메서드를 실행하기 전에 UI WebView가 로드될 때까지 기다려야 합니다. 이 스레드에 언급된 세마포어 메서드와 함께 GCD를 사용하여 메인 스레드에 대한 UI WebView 준비 검사를 수행하여 이 작업을 수행할 수 있었습니다.최종 코드는 다음과 같습니다.
-(void)myMethod {
if (![self isWebViewLoaded]) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BOOL isWebViewLoaded = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!isWebViewLoaded) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.0) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
isWebViewLoaded = [self isWebViewLoaded];
});
[NSThread sleepForTimeInterval:0.1];//check again if it's loaded every 0.1s
}
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
});
});
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
}
//Run rest of method here after web view is loaded
}
언급URL : https://stackoverflow.com/questions/4326350/how-do-i-wait-for-an-asynchronously-dispatched-block-to-finish
'programing' 카테고리의 다른 글
열 1과 열 A를 동시에 동결 (0) | 2023.04.26 |
---|---|
프레임워크의 런타임 대상을 찾을 수 없습니다.대상 런타임 중 하나와 호환되는 NETCoreApp= v1 (0) | 2023.04.26 |
IIS/ASP에 대한 모든 사용자 계정은 무엇입니까?NET과 그것들은 어떻게 다릅니까? (0) | 2023.04.26 |
FTP 지원을 Eclipse에 추가하려면 어떻게 해야 합니까? (0) | 2023.04.26 |
WPF 텍스트 블록에서 내 텍스트를 여러 줄로 표시하려면 어떻게 해야 합니까? (0) | 2023.04.26 |