2012-09-18 25 views
10

Đây là vấn đề của tôi. Khi ứng dụng của tôi vào nền tôi muốn nó thực hiện một chức năng sau một khoảng thời gian nhất định. Đây là những gì tôi làm:Ngăn tác vụ nền dispatch_after() bị thi hành

- (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 biến được khai báo trong tập tin myAppDelegate.h như thế này:

UIBackgroundTaskIdentifier taskIdentifier; 

Tất cả mọi thứ hoạt động như nó phải, tôi thấy rằng giao diện điều khiển in Hello vừa phải sau 30 giây đã biến mất. Nhưng tôi không muốn doSomething được thực hiện nếu ứng dụng đi vào nền trước cho đến khi hết 30 giây. Vì vậy, tôi cần phải hủy bỏ nó. Đây là cách tôi làm điều đó:

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

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

Nhưng tiếc là nó không hủy doSomething, nó vẫn được thực hiện. Tôi đang làm gì sai? Làm thế nào để tôi hủy bỏ chức năng đó?

Trả lời

13

Tại sao lại sử dụng GCD? Bạn chỉ có thể sử dụng một số NSTimer và vô hiệu hóa nó khi ứng dụng của bạn quay trở lại tiền cảnh.

+0

Bạn là đúng, đó là giải pháp tốt hơn! –

+0

Cảm ơn rất nhiều! Quá dễ! Tôi nên nghĩ về điều đó bản thân mình –

3

endBackgroundTask không hủy tác vụ nền. Nó cho hệ thống biết rằng tác vụ nền của bạn đã hoàn thành. Vì vậy, bạn nên gọi điều này sau khi "làm một cái gì đó". Để ngăn chặn doSomething từ được thực thi nếu ứng dụng của bạn là ở mặt trước một lần nữa, bạn có thể sử dụng isRunningInBackground lá cờ của bạn:

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

Đây là câu trả lời đúng – juancazalla

2

Tôi nghĩ rằng bạn không thể hủy bỏ nó, nhưng bạn có thể kiểm tra trạng thái nhiệm vụ trước khi thực hiện các doSomething

dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) 
    { 

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

    }); 
11

Một cách tiếp cận khác nhau chút OK, vì vậy, với tất cả các câu trả lời thu được, và giải pháp khả thi, có vẻ như tốt nhất cho trường hợp này (bảo quản đơn giản) đang kêu gọi performSelector:withObject:afterDelay: và hủy bỏ nó với cancelPreviousPerformRequestsWithTarget: cuộc gọi khi mong muốn.

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

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

Câu trả lời này phải được niêm yết ở đây:: - Trong trường hợp của tôi ngay trước khi lên kế hoạch tiếp theo gọi trì hoãn cancel dispatch_after() method?, nhưng đó là đóng cửa như là một bản sao (nó thực sự không phải là). Dù sao, đây là một nơi mà google trả về cho "dispatch_after cancel", vì vậy ...

Câu hỏi này khá cơ bản và tôi chắc chắn có những người muốn một giải pháp thực sự chung chung mà không cần đến các nền tảng cụ thể như runloop timers, boolean instance và/hoặc magic block nặng. GCD có thể được sử dụng như một thư viện C thông thường và có thể không có thứ gì như một bộ đếm thời gian cả.

May mắn thay, có cách để hủy bất kỳ khối công văn nào trong bất kỳ chương trình đời nào.

  1. Chúng tôi phải đính kèm xử lý động vào từng khối mà chúng tôi chuyển đến dispatch_after (hoặc dispatch_async, không thực sự quan trọng).
  2. Tay cầm này phải tồn tại cho đến khi khối thực sự được kích hoạt.
  3. Quản lý bộ nhớ cho xử lý này không rõ ràng - nếu khối giải phóng xử lý, sau đó chúng tôi có thể dereference lơ lửng con trỏ sau này, nhưng nếu chúng tôi giải phóng nó, khối có thể làm điều đó sau.
  4. Vì vậy, chúng tôi phải chuyển quyền sở hữu theo yêu cầu.
  5. Có 2 khối - một khối là khối điều khiển sẽ kích hoạt và thứ hai là số tiền trọng tải có thể bị hủy.

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

Vậy là xong.

Điểm chính là "chủ sở hữu" nên thu thập tay cầm cho đến khi nó không cần chúng nữa. dispatch_cancel_h() hoạt động như một destructor [có khả năng trì hoãn] cho một xử lý.

C chủ sở hữu dụ:

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 
} 

Objective-C ARC dụ:

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

Ghi chú:

  • dispatch_after() ban đầu được giữ lại hàng đợi, vì vậy nó sẽ tồn tại cho đến khi tất cả các khối điều khiển được thực hiện.
  • async_handles được giải phóng nếu tải trọng bị hủy (hoặc thời gian tồn tại của chủ sở hữu đã kết thúc) VÀ khối điều khiển đã được thực thi.
  • Chi phí bộ nhớ động của async_handle hoàn toàn nhỏ so với cấu trúc bên trong dispatch_after() và dispatch_queue_t, giữ lại một mảng khối thực sự được gửi và khử chúng khi thích hợp.
  • Bạn có thể nhận thấy rằng shouldCall và shouldFree thực sự là cùng một cờ đảo ngược. Nhưng trường hợp chủ sở hữu của bạn có thể vượt qua quyền sở hữu và thậm chí - [dealloc] chính nó mà không thực sự hủy bỏ khối tải trọng, nếu những điều này không phụ thuộc vào "tự" hoặc dữ liệu liên quan đến chủ sở hữu khác. Điều này có thể được thực hiện với đối số shouldCallAnyway bổ sung để dispatch_cancel_h().
  • Lưu ý cảnh báo: giải pháp này cũng thiếu đồng bộ cờ didXYZ và có thể gây ra một cuộc chạy đua giữa khối điều khiển và quy trình hủy. Sử dụng OSAtomicOr32Barrier() & đồng để đồng bộ hóa.
+0

Lưu ý: mã trong câu trả lời đã được viết cho ARC. Trong MRC hoặc pure C, trọng tải phải rõ ràng Block_retain'ed trong dispatch_after_h() và Block_release'd trong khối điều khiển để sửa chữa deallocation sớm. – user3125367

1

Bạn hoàn toàn có thể hủy bằng cờ. Tôi đã viết một chức năng nhỏ để làm điều đó, về cơ bản chúng tôi vượt qua một con trỏ BOOL để kiểm soát liệu các khối bị hủy bỏ.

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

Tôi đã trả lời câu hỏi về việc hủy dispatch_afterhere. Nhưng khi tôi google tìm giải pháp, nó cũng trả lại cho tôi chủ đề này, vì vậy ...

iOS 8 và OS X Yosemite giới thiệu dispatch_block_cancel cho phép bạn hủy chặn trước khi bắt đầu thực thi. Bạn có thể xem chi tiết về câu trả lời đó here

Sử dụng dispatch_after nhận lợi ích về việc sử dụng các biến mà bạn đã tạo trong hàm đó và trông liền mạch. Nếu bạn sử dụng NSTimer thì bạn phải tạo một Selector và gửi các biến mà bạn cần vào userInfo hoặc biến các biến đó thành các biến toàn cục.

+0

Đây thực sự là câu trả lời chính xác ngay bây giờ, vì câu trả lời ban đầu về bản chất là "Đừng làm vậy. Làm theo cách này thay thế." –

0

Đây là phản hồi hơi chung chung hơn mặc dù tôi nghĩ rằng nó vẫn trả lời câu hỏi của bạn một cách hợp lý.Thay vì "isRunningInBackground", hãy giữ thời gian bạn đặt nền trước/nền trước; sử dụng thời gian bạn đã làm nền dưới dạng biến cục bộ cho dispatch_after. Kiểm tra bên trong dispatch_after của bạn trước khi gọi doSomething. Vấn đề cụ thể hơn của tôi bên dưới ....

Tôi đang thực hiện một loạt các hoạt ảnh cần khởi chạy vào các thời điểm khác nhau và sẽ dẫm lên nhau nếu tôi sử dụng setBeginTime trong khi đảm bảo lớp mô hình được cập nhật cho bản trình bày lớp vào đúng thời điểm, vv ... vì vậy tôi bắt đầu sử dụng dispatch_after, ngoại trừ không thể "hủy bỏ" chúng (mà quan trọng đối với tôi đặc biệt là khi tôi muốn khởi động lại loạt các hình ảnh động).

Tôi đang giữ một CFTimeInterval startCalled; trên dụ UIView của tôi, và sau đó bên trong -(void) start của tôi, tôi có:

startCalled = CACurrentMediaTime(); 
CFTimeInterval thisStartCalled = startCalled; 

Vào đầu mỗi dispatch_after khối, tôi sau đó có:

if (thisStartCalled != startCalled) return; 

Điều này cho phép tôi thiết lập mọi thứ trong một lần, nhưng chỉ có các lớp mô hình của tôi cập nhật bên trong các khối Giao dịch tự động của họ tại thời gian chúng bắt đầu.

1

Kể từ iOS 10Swift 3 GCD DispatchWorkItem là hủy. Chỉ cần giữ một ví dụ để các hạng mục công trình và kiểm tra xem nó đã không được hủy, sau đó hủy bỏ nó:

// 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() 
} 
Các vấn đề liên quan