मैं एक अतुल्यकालिक रूप से भेजे गए ब्लॉक को समाप्त करने के लिए कैसे प्रतीक्षा करूं?


180

मैं कुछ कोड का परीक्षण कर रहा हूं जो ग्रैंड सेंट्रल डिस्पैच का उपयोग करके अतुल्यकालिक प्रसंस्करण करता है। परीक्षण कोड इस तरह दिखता है:

[object runSomeLongOperationAndDo:^{
    STAssert
}];

परीक्षण खत्म होने के लिए परीक्षण का इंतजार करना पड़ता है। मेरा वर्तमान समाधान इस तरह दिखता है:

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

जो थोड़ा कच्चा दिखता है, क्या आप बेहतर तरीके से जानते हैं? मैं कतार को उजागर कर सकता था और फिर कॉल करके ब्लॉक कर सकता था dispatch_sync:

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

... लेकिन यह शायद बहुत अधिक पर उजागर हो रहा है object

जवाबों:


302

एक का उपयोग करने की कोशिश कर रहा है 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:तय हो कि ऑपरेशन वास्तव में थ्रेडिंग को मेरिट में लाने के लिए पर्याप्त नहीं है और इसके बजाय समकालिक रूप से चलता है।


61
इस कोड ने मेरे लिए काम नहीं किया। मेरा STAssert कभी भी निष्पादन नहीं करेगा। मैं बदलने के लिए किया था dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);के साथwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
nicktmro

41
ऐसा शायद इसलिए है क्योंकि आपका पूरा होने वाला ब्लॉक मुख्य कतार में भेज दिया जाता है? कतार अर्धवृत्त की प्रतीक्षा में अवरुद्ध है और इसलिए ब्लॉक को कभी निष्पादित नहीं करता है। बिना अवरोध के मुख्य कतार पर भेजने के बारे में यह प्रश्न देखें ।
जुल

3
मैंने @Zoul & nictmro के सुझाव का पालन किया। लेकिन ऐसा लग रहा है कि यह गतिरोध की स्थिति में जा रहा है। टेस्ट केस '- [BlockTestTest testAsync]' शुरू हुआ। लेकिन कभी खत्म नहीं हुआ
NSCry

3
क्या आपको एआरसी के तहत सेमाफोर जारी करने की आवश्यकता है?
पीटर वार्बो

14
यह ठीक वही है जिसकी मुझे तलाश थी। धन्यवाद! @PeterWarbo नहीं तुम नहीं। एआरसी का उपयोग एक
प्रेषण_रेल

29

अन्य जवाबों में पूरी तरह से कवर किए गए सेमीफोर तकनीक के अलावा, हम अब अतुल्यकालिक परीक्षण करने के लिए 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 , इन परीक्षण सूटों में, हमें यह सुनिश्चित करना चाहिए कि परीक्षण तब तक समाप्त नहीं होगा जब तक कि एसिंक्रोनस कॉल नहीं किया जाता है)। यह उन दुर्लभ स्थितियों में से एक है जहां मुख्य धागे को अवरुद्ध करने के लिए अर्ध-तकनीक आवश्यक हो सकती है।

तो इस मूल प्रश्न के लेखक के लिए मेरी क्षमायाचना के साथ, जिनके लिए सेमाफोर तकनीक ध्वनि है, मैं उन सभी नए डेवलपर्स के लिए यह चेतावनी लिखता हूं, जो इस सेमाफोर तकनीक को देखते हैं और इसे असंगत से निपटने के लिए एक सामान्य दृष्टिकोण के रूप में अपने कोड में लागू करने पर विचार करते हैं। विधियाँ: बताया जा सकता है कि दस में से नौ बार, सेमाफोर तकनीक है नहीं हैअतुल्यकालिक संचालन का सामना करते समय सबसे अच्छा तरीका। इसके बजाय, अपने आप को पूर्ण ब्लॉक / क्लोजर पैटर्न, साथ ही प्रतिनिधि-प्रोटोकॉल पैटर्न और सूचनाओं से परिचित कराएं। ये अक्सर अतुल्यकालिक कार्यों से निपटने के बेहतर तरीके होते हैं, बजाय इसके कि वे समकालिक व्यवहार करें। आमतौर पर अच्छे कारण हैं कि अतुल्यकालिक कार्यों को अतुल्यकालिक व्यवहार करने के लिए डिज़ाइन किया गया था, इसलिए उन्हें समकालिक व्यवहार करने की कोशिश करने के बजाय सही अतुल्यकालिक पैटर्न का उपयोग करें।


1
मुझे लगता है कि अब यह स्वीकृत उत्तर होना चाहिए। यहाँ डॉक्स भी हैं: developer.apple.com/library/prerelease/ios/documentation/…
hris.to

इस बारे में मेरा एक सवाल है। मुझे कुछ अतुल्यकालिक कोड मिले हैं जो एक एकल दस्तावेज़ डाउनलोड करने के लिए लगभग एक दर्जन AFNetworking डाउनलोड कॉल करता है। मैं एक पर डाउनलोड शेड्यूल करना चाहता हूं NSOperationQueue। जब तक मैं एक सेमाफोर जैसी किसी चीज़ का उपयोग नहीं करता, तब तक दस्तावेज़ डाउनलोड NSOperationएस सभी तुरंत पूरा होता दिखाई देगा और डाउनलोड की कोई वास्तविक कतार नहीं होगी - वे बहुत अधिक समवर्ती रूप से आगे बढ़ेंगे, जो मुझे नहीं चाहिए। क्या यहां अर्धकुंवारी उचित हैं? या क्या NSOperations को दूसरों के अतुल्यकालिक अंत की प्रतीक्षा करने का एक बेहतर तरीका है? या कुछ और?
बेंजो

नहीं, इस स्थिति में सेमाफोर का उपयोग न करें। यदि आपके पास ऑपरेशन कतार है जिसमें आप AFHTTPRequestOperationवस्तुओं को जोड़ रहे हैं , तो आपको तब एक पूरा ऑपरेशन बनाना चाहिए (जो आप अन्य कार्यों पर निर्भर करेंगे)। या प्रेषण समूहों का उपयोग करें। BTW, आप कहते हैं कि आप उन्हें समवर्ती रूप से नहीं चलाना चाहते हैं, जो ठीक है अगर आपको इसकी आवश्यकता है, लेकिन आप समवर्ती रूप से करने के बजाय इस क्रमिक रूप से गंभीर प्रदर्शन दंड का भुगतान करते हैं। मैं आम तौर पर maxConcurrentOperationCount4 या 5 का उपयोग करता हूं
रोब

28

मैं हाल ही में फिर से इस मुद्दे पर आया हूं और निम्न श्रेणी पर लिखा है 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
}];

24

आम तौर पर इनमें से किसी भी उत्तर का उपयोग नहीं करते हैं, वे अक्सर पैमाने पर नहीं होंगे (यहां और अपवाद हैं, निश्चित रूप से)

ये दृष्टिकोण असंगत हैं कि जीसीडी कैसे काम करने का इरादा रखता है और न ही गतिरोध पैदा करेगा और / या फिर नॉनस्टॉप मतदान द्वारा बैटरी को मार देगा।

दूसरे शब्दों में, अपने कोड को फिर से व्यवस्थित करें ताकि परिणाम के लिए कोई समकालिक प्रतीक्षा न हो, लेकिन इसके बजाय राज्य के परिवर्तन (जैसे कॉलबैक / प्रतिनिधि प्रोटोकॉल, उपलब्ध होने, दूर होने, त्रुटियों, आदि) के बारे में सूचित किया जाए। (अगर आपको कॉलबैक नर्क पसंद नहीं है, तो इन्हें ब्लॉक में फिर से बनाया जा सकता है।) क्योंकि यह ऐप के बाकी हिस्सों में वास्तविक व्यवहार को उजागर करने की तुलना में इसे झूठे बहाने के पीछे छिपा देता है।

इसके बजाय, NSNotificationCenter का उपयोग करें , अपने वर्ग के लिए कॉलबैक के साथ एक कस्टम प्रतिनिधि प्रोटोकॉल को परिभाषित करें। और अगर आपको प्रतिनिधि कॉलबैक के साथ सभी तरह से मैकिंग पसंद नहीं है, तो उन्हें एक ठोस प्रॉक्सी वर्ग में लपेटें जो कस्टम प्रोटोकॉल को लागू करता है और गुणों में विभिन्न ब्लॉक को बचाता है। संभवत: सुविधा निर्माणकर्ता भी प्रदान करते हैं।

प्रारंभिक काम थोड़ा अधिक है, लेकिन यह लंबे समय में भयानक दौड़ की स्थिति और बैटरी-हत्या मतदान को कम कर देगा।

(उदाहरण के लिए पूछिए मत, क्योंकि यह तुच्छ है और हमें उद्देश्य-सी मूल बातें भी सीखने के लिए समय का निवेश करना था।)


1
यह एक महत्वपूर्ण चेतावनी है क्योंकि obj-C डिजाइन पैटर्न और परीक्षण क्षमता, भी
बूटमेकर

8

यहाँ एक निफ्टी ट्रिक है जो एक सेमाफोर का उपयोग नहीं करता है:

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

आप जो करते हैं वह dispatch_syncएक ब्लॉक प्रेषण के साथ एक खाली ब्लॉक का उपयोग करके प्रतीक्षा करने के लिए एक सीरियल प्रेषण कतार पर प्रतीक्षा करें जब तक कि ए-सिंक्रोनस ब्लॉक पूरा नहीं हुआ है।


इस उत्तर के साथ समस्या यह है कि यह ओपी की मूल समस्या को संबोधित नहीं करता है, जो यह है कि जिस एपीआई का उपयोग करने की आवश्यकता होती है वह एक पूर्णहैंडलर को एक तर्क के रूप में लेता है और तुरंत लौटता है। इस उत्तर के एसिंक्स ब्लॉक के अंदर उस एपीआई को कॉल करना तुरंत वापस आ जाएगा, हालांकि पूर्णहैंडलर अभी तक नहीं चला था। तब सिंक ब्लॉक पूरा होने से पहले निष्पादित हो जाएगा।
BTRUE

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

2

वहाँ 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];

1

यहाँ मेरा एक परीक्षण से एक विकल्प है:

__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);

1
उपरोक्त कोड में कोई त्रुटि है। "इस विधि को कॉल करने से पहले आपको रिसीवर को लॉक करना होगा" के लिए NSCondition प्रलेखन से -waitUntilDate:। तो -unlockबाद में होना चाहिए -waitUntilDate:
पैट्रिक

यह किसी भी चीज़ के लिए पैमाने पर नहीं है जो कई थ्रेड्स का उपयोग करता है या कतारें चलाता है।

0
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]];
}

इसने मेरे लिए यह किया।


3
हालांकि, यह उच्च सीपीयू उपयोग का कारण बनता है
केविन

4
@kevin यूप, यह यहूदी बस्ती मतदान है जो बैटरी को मार देगा।

@ बैरी, यह कैसे अधिक बैटरी की खपत करता है। कृपया मार्गदर्शन करें।
pkc456

@ pkc456 एक कंप्यूटर विज्ञान की किताब में एक नज़र है कि मतदान और अतुल्यकालिक अधिसूचना के बीच अंतर कैसे काम करता है। सौभाग्य।

2
साढ़े चार साल बाद और मैंने जो ज्ञान और अनुभव प्राप्त किया है, मैं अपने जवाब की सिफारिश नहीं करूंगा।

0

कभी-कभी, टाइमआउट लूप भी सहायक होते हैं। क्या आप तब तक प्रतीक्षा कर सकते हैं जब तक आपको async कॉलबैक विधि से कुछ (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];
}

1
समान समस्या: बैटरी जीवन विफल।

1
@Barry ज़रूर करें भले ही आपने कोड को देखा हो। TIMEOUT_SECONDS अवधि है जिसके भीतर यदि async कॉल का जवाब नहीं है, तो यह लूप को तोड़ देगा। वह गतिरोध तोड़ने के लिए हैक है। यह कोड बैटरी को मारे बिना पूरी तरह से काम करता है।
खुलजा सिम सिम

0

समस्या का बहुत प्राथमिक समाधान:

void (^nextOperationAfterLongOperationBlock)(void) = ^{

};

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

0

स्विफ्ट 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
}

-1

मुझे अपना तरीका चलाने से पहले एक UIWebView लोड होने तक इंतजार करना होगा, मैं इस काम को प्राप्त करने में सक्षम था UIWebView इस धागे में वर्णित सेमीफोर विधियों के साथ जीसीडी का उपयोग करके मुख्य थ्रेड पर तैयार किए गए चेक। अंतिम कोड इस तरह दिखता है:

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

}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.