2012-09-18 19 views
10

यह मेरा मुद्दा है। जब मेरा एप्लिकेशन पृष्ठभूमि में प्रवेश करता है तो मैं चाहता हूं कि यह कुछ निश्चित अवधि के बाद एक कार्य करे। यह मैं क्या कर रहा है:निष्पादित होने से dispatch_after() पृष्ठभूमि कार्य को रोकें

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    isRunningInBackground = YES; 

    taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; 

    int64_t delayInSeconds = 30; 
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
    dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 
     [self doSomething]; 
    }); 
} 

- (void)doSomething 
{ 
    NSLog(@"HELLO"); 
} 

taskIdentifier चर इस तरह myAppDelegate.h फ़ाइल में घोषित किया जाता है:

UIBackgroundTaskIdentifier taskIdentifier; 

सब कुछ काम करता है के रूप में यह है, मैं उस सांत्वना प्रिंट नमस्ते 30 सिर्फ सही के बाद सेकंड देखने के लिए चाहिए चले गए हैं। लेकिन मैं doSomething को निष्पादित नहीं करना चाहता हूं यदि ऐप 30 सेकंड तक फोरग्राउंड में प्रवेश करता है। तो मुझे इसे रद्द करने की जरूरत है। यह वह जगह है मैं कैसे कर कि:

- (void)applicationWillEnterForeground:(UIApplication *)application 
{  
    isRunningInBackground = NO; 
    [self stopBackgroundExecution]; 
} 

- (void)stopBackgroundExecution 
{ 
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier]; 
    taskIdentifier = UIBackgroundTaskInvalid; 
} 

लेकिन दुर्भाग्य से यह doSomething रद्द नहीं करता, यह अभी भी किया जाता है। मैं क्या गलत कर रहा हूं? मैं उस समारोह को कैसे रद्द करूं?

उत्तर

13

जीसीडी का उपयोग क्यों करें? आप बस NSTimer का उपयोग कर सकते हैं और जब आपका ऐप फोरगॉउंड पर वापस आ जाता है तो उसे अमान्य कर दें।

+0

आप ठीक कह रहे हैं, कि बेहतर समाधान है कर रहा है! –

+0

बहुत बहुत धन्यवाद! बहुत आसन! मुझे अपने बारे में सोचना चाहिए था –

3

endBackgroundTask पृष्ठभूमि कार्य को रद्द नहीं करता है। यह सिस्टम को बताता है कि आपका पृष्ठभूमि कार्य समाप्त हो गया है। इसलिए आपको इसे "कुछ करने" के बाद कॉल करना चाहिए। निष्पादित किया जा रहा से doSomething को रोकने के लिए कि आपके ऐप में फिर से अग्रभूमि में है, आप अपने isRunningInBackground झंडा इस्तेमाल कर सकते हैं:

dispatch_after(popTime, dispatch_get_global_queue(...), ^(void) { 
    if (isRunningInBackground) { 
     [self doSomething]; 
    } 
    [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier]; 
}); 
+0

यह सही जवाब – juancazalla

2

मुझे लगता है कि आप इसे रद्द नहीं कर सकते, लेकिन आप DoSomething

निष्पादित करने से पहले कार्य राज्य जाँच कर सकते हैं
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 

    if(taskIdentifier != UIBackgroundTaskInvalid) { 
     [self doSomething]; 
    } 

    }); 
11

थोड़ा अलग दृष्टिकोण ठीक है, हां, तो एकत्र सभी प्रश्नों के उत्तर, और संभावित समाधानों के साथ, इस मामले (संरक्षण सादगी) performSelector:withObject:afterDelay: बुला और cancelPreviousPerformRequestsWithTarget: कॉल के साथ इसे रद्द करने से जब वांछित है के लिए सबसे अच्छा लगता है।

[NSObject cancelPreviousPerformRequestsWithTarget: self selector:@selector(myDelayedMethod) object: self]; 

[self performSelector:@selector(myDelayedMethod) withObject: self afterDelay: desiredDelay]; 
5

इस उत्तर यहां पोस्ट किया जाना चाहिए:: - मेरे मामले में बस से पहले अगले शेड्यूल करते समय कॉल देरी cancel dispatch_after() method?, लेकिन वह (यह वास्तव में नहीं है) डुप्लिकेट के रूप में बंद है। वैसे भी, यह एक ऐसा स्थान है जहां Google "dispatch_after cancel" के लिए रिटर्न देता है, इसलिए ...

यह प्रश्न बहुत मौलिक है और मुझे यकीन है कि ऐसे लोग हैं जो विभिन्न प्लेटफ़ॉर्म-विशिष्टताओं का उपयोग किए बिना वास्तव में सामान्य समाधान चाहते हैं रनलोप टाइमर, इंस्टेंस-निहित बूलियन और/या भारी ब्लॉक जादू। जीसीडी नियमित सी पुस्तकालय के रूप में इस्तेमाल किया जा सकता है और टाइमर के रूप में ऐसी कोई चीज़ नहीं हो सकती है।

सौभाग्य से, किसी भी जीवनकाल योजना में किसी भी प्रेषण ब्लॉक को रद्द करने का एक तरीका है।

  1. हमें प्रत्येक ब्लॉक को एक गतिशील हैंडल संलग्न करना है जिसे हम dispatch_after (या dispatch_async, वास्तव में मायने रखते हैं) में पास नहीं करते हैं।
  2. यह संभाल तब तक मौजूद होना चाहिए जब तक कि ब्लॉक वास्तव में निकाल दिया न जाए।
  3. इस हैंडल के लिए मेमोरी प्रबंधन इतना स्पष्ट नहीं है - अगर ब्लॉक हैंडल को मुक्त करता है, तो हम बाद में पॉइंटर को खतरे में डाल सकते हैं, लेकिन अगर हम इसे मुक्त करते हैं, तो ब्लॉक बाद में ऐसा कर सकता है।
  4. तो, हमें मांग पर स्वामित्व पास करना होगा।
  5. 2 ब्लॉक हैं - एक नियंत्रण ब्लॉक है जो किसी भी तरह से आग लगता है और दूसरा पेलोड है जिसे रद्द किया जा सकता है।

struct async_handle { 
    char didFire;  // control block did fire 
    char shouldCall; // control block should call payload 
    char shouldFree; // control block is owner of this handle 
}; 

static struct async_handle * 
dispatch_after_h(dispatch_time_t when, 
       dispatch_queue_t queue, 
       dispatch_block_t payload) 
{ 
    struct async_handle *handle = malloc(sizeof(*handle)); 

    handle->didFire = 0; 
    handle->shouldCall = 1; // initially, payload should be called 
    handle->shouldFree = 0; // and handles belong to owner 

    payload = Block_copy(payload); 

    dispatch_after(when, queue, ^{ 
     // this is a control block 

     printf("[%p] (control block) call=%d, free=%d\n", 
      handle, handle->shouldCall, handle->shouldFree); 

     handle->didFire = 1; 
     if (handle->shouldCall) payload(); 
     if (handle->shouldFree) free(handle); 
     Block_release(payload); 
    }); 

    return handle; // to owner 
} 

void 
dispatch_cancel_h(struct async_handle *handle) 
{ 
    if (handle->didFire) { 
     printf("[%p] (owner) too late, freeing myself\n", handle); 
     free(handle); 
    } 
    else { 
     printf("[%p] (owner) set call=0, free=1\n", handle); 
     handle->shouldCall = 0; 
     handle->shouldFree = 1; // control block is owner now 
    } 
} 

यह है कि।

मुख्य बिंदु यह है कि "मालिक" को हैंडल एकत्र करना चाहिए जब तक कि उन्हें अब और आवश्यकता न हो। dispatch_cancel_h() एक हैंडल के लिए [संभावित रूप से स्थगित] विनाशक के रूप में काम करता है।

सी मालिक उदाहरण:

size_t n = 100; 
struct after_handle *handles[n]; 

for (size_t i = 0; i < n; i++) 
    handles[i] = dispatch_after_h(when, queue, ^{ 
     printf("working\n"); 
     sleep(1); 
    }); 

... 

// cancel blocks when lifetime is over! 

for (size_t i = 0; i < n; i++) { 
    dispatch_cancel_h(handles[i]); 
    handles[i] = NULL; // not our responsibility now 
} 

ऑब्जेक्टिव-सी एआरसी उदाहरण:

- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     queue = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL); 
     handles = [[NSMutableArray alloc] init]; 
    } 
    return self; 
} 

- (void)submitBlocks 
{ 
    for (int i = 0; i < 100; i++) { 
     dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (random() % 10) * NSEC_PER_SEC); 

     __unsafe_unretained id this = self; // prevent retain cycles 

     struct async_handle *handle = dispatch_after_h(when, queue, ^{ 
      printf("working (%d)\n", [this someIntValue]); 
      sleep(1); 
     }); 
     [handles addObject:[NSValue valueWithPointer:handle]]; 
    } 
} 

- (void)cancelAnyBlock 
{ 
    NSUInteger i = random() % [handles count]; 
    dispatch_cancel_h([handles[i] pointerValue]); 
    [handles removeObjectAtIndex:i]; 
} 

- (void)dealloc 
{ 
    for (NSValue *value in handles) { 
     struct async_handle *handle = [value pointerValue]; 
     dispatch_cancel_h(handle); 
    } 
    // now control blocks will never call payload that 
    // dereferences now-dangling self/this. 
} 

नोट्स:

  • dispatch_after() मूल रूप से, कतार को बरकरार रखे हुए है, तो यह सब जब तक उपलब्ध नहीं होगा नियंत्रण ब्लॉक निष्पादित कर रहे हैं।
  • async_handles को मुक्त किया जाता है अगर पेलोड रद्द हो जाता है (या मालिक का जीवनकाल खत्म हो गया था) और नियंत्रण ब्लॉक निष्पादित किया गया था।
  • async_handle की गतिशील मेमोरी ओवरहेड dispatch_after() और dispatch_queue_t की आंतरिक संरचनाओं की तुलना में बिल्कुल मामूली है, जो सबमिट किए जाने वाले ब्लॉक की वास्तविक सरणी बनाए रखती है और उपयुक्त होने पर उन्हें हटा देती है।
  • आप देख सकते हैं कि चाहिएकॉल और चाहिएफ्री वास्तव में एक ही उलटा झंडा है। लेकिन यदि आपका मालिक "स्वयं" या अन्य मालिक से संबंधित डेटा पर निर्भर नहीं है, तो आपके मालिक का उदाहरण स्वामित्व और यहां तक ​​कि - [dealloc] खुद को पेलोड ब्लॉक को रद्द किए बिना भी पारित कर सकता है। इसे dispatch_cancel_h() को अतिरिक्त shouldCallAnyway तर्क के साथ कार्यान्वित किया जा सकता है।
  • चेतावनी नोट: इस समाधान में भी डीएक्सएक्सजेड झंडे के सिंक्रनाइज़ेशन की कमी है और नियंत्रण ब्लॉक और रद्दीकरण दिनचर्या के बीच दौड़ हो सकती है। OSAtomicOr32Barrier() & सह सिंक्रनाइज़ करने के लिए सह का उपयोग करें।
+0

नोट: उत्तर में कोड एआरसी के लिए लिखा गया था। एमआरसी या शुद्ध सी में, पेलोड को स्पष्ट रूप से dispatch_after_h() में ब्लॉक_रेरेन किया जाना चाहिए और समय-समय पर हटाने के लिए नियंत्रण ब्लॉक में Block_release'd किया जाना चाहिए। – user3125367

1

आप इसे ध्वज के साथ बिल्कुल रद्द कर सकते हैं। मैंने ऐसा करने के लिए एक छोटा सा कार्य लिखा, मूल रूप से हम ब्लॉक को रद्द करने के लिए एक बॉल पॉइंटर पास करते हैं या नहीं।

void dispatch_with_cancellation(void (^block)(), BOOL* cancellation) { 
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); 
    dispatch_after(time, dispatch_get_main_queue(), ^{ 
     if (!*cancellation) { 
      block(); 
     } 
    }); 
} 

int main(int argc, char *argv[]) { 
    @autoreleasepool { 
     void (^block)() = ^{ 
      NSLog(@"%@", @"inside block"); 
     }; 
     BOOL cancellation; 
     dispatch_with_cancellation(block, &cancellation); 
     // cancel the block by setting the BOOL to YES. 
     *&cancellation = YES; 
     [[NSRunLoop currentRunLoop] run]; 
    } 
} 
7

मैंने जवाब सवाल के बारे में dispatch_afterhere रद्द कर दें। लेकिन जब मैं समाधान ढूंढने के लिए Google हूं तो यह मुझे इस धागे पर भी लौटाता है, इसलिए ...

आईओएस 8 और ओएस एक्स योसेमेट ने dispatch_block_cancel पेश किया जो आपको निष्पादन शुरू करने से पहले ब्लॉक को रद्द करने की अनुमति देता है। आप उस उत्तर here

dispatch_after का उपयोग करके उस फ़ंक्शन में बनाए गए चर का उपयोग करने और निर्बाध दिखने के बारे में लाभ प्राप्त कर सकते हैं। यदि आप NSTimer का उपयोग करते हैं तो आपको Selector बनाना होगा और userInfo में वेरिएबल्स भेजना होगा या उस चर को वैश्विक चर में बदलना होगा।

+0

यह वास्तव में सही जवाब है, क्योंकि मूल उत्तर अनिवार्य रूप से है "ऐसा मत करो। इसके बजाय इसे इस तरह से करें।" –

0

यह कुछ और सामान्य प्रतिक्रिया है, हालांकि मुझे लगता है कि यह अभी भी आपके प्रश्न का उत्तर उचित रूप से उत्तर देता है।"IsRunningInBackground" के बजाय आपके द्वारा पिछली पृष्ठभूमि/अग्रभूमि का समय रखें; स्थानीय चर के रूप में प्रेषण के समय प्रेषण के समय का उपयोग करें। कुछ करने से पहले अपने प्रेषण के अंदर जांचें। नीचे मेरी और अधिक विशिष्ट समस्या ....

मैं एनिमेशन की एक लंबी ढेर कर रहा हूं जिसे विभिन्न समय पर लॉन्च करने की आवश्यकता है और अगर मैं सेटबिनटाइम का उपयोग करता हूं तो यह सुनिश्चित करता है कि मॉडल परत प्रेजेंटेशन में अपडेट हो सही समय पर परत, आदि ... इसलिए मैंने dispatch_after का उपयोग शुरू किया, सिवाय इसके कि उन्हें "रद्द" नहीं किया जा सकता था (जो मेरे लिए महत्वपूर्ण था, खासकर जब मैं एनिमेशन की श्रृंखला को पुनरारंभ करना चाहता था)।

मैं अपने -(void) start के अंदर मेरी UIView उदाहरण पर एक CFTimeInterval startCalled; रखने कर रहा हूँ, और फिर मैं:

startCalled = CACurrentMediaTime(); 
CFTimeInterval thisStartCalled = startCalled; 

प्रत्येक dispatch_after ब्लॉक की शुरुआत में, मैं तो है:

if (thisStartCalled != startCalled) return; 

यह मुझे सब कुछ एक ही समय में स्थापित करने देता है, लेकिन केवल मेरे मॉडल परतों को पर शुरू होने के समय उनके कैटर्रैक्शन ब्लॉक के अंदर अपडेट किया गया है।

1

आईओएस 10 और स्विफ्ट 3 GCD के बाद से DispatchWorkItem रद्द करने योग्य हैं। बस काम आइटम के लिए एक उदाहरण रखना और देखें कि क्या यह रद्द नहीं किया गया और उसके बाद इसे रद्द:

// Create a work item 
let work = DispatchWorkItem { 
    print("Work to be done or cancelled") 
} 

// Dispatch the work item for executing after 2 seconds 
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: work) 

// Later cancel the work item 
if !work.isCancelled { 
    print("Work:\(work)") 
    dispatchPrecondition(condition: .onQueue(.main)) 
    work.cancel() 
} 
संबंधित मुद्दे