この回答はここに投稿する必要があります:dispatch_after()メソッドをキャンセルしますか?、しかしそれは重複として閉じられます(実際にはそうではありません)。とにかく、これはグーグルが「dispatch_aftercancel」のために返す場所なので...
この質問は非常に基本的なものであり、runloopタイマー、インスタンスに含まれるブール値、重いブロックマジックなど、さまざまなプラットフォーム固有のソリューションに頼らずに、真に汎用的なソリューションを求める人々がいると確信しています。GCDは通常のCライブラリとして使用でき、タイマーのようなものはまったくないかもしれません。
幸いなことに、任意のライフタイムスキームで任意のディスパッチブロックをキャンセルする方法があります。
- dispatch_afterに渡す各ブロックに動的ハンドルをアタッチする必要があります(またはdispatch_async、実際には重要ではありません)。
- このハンドルは、ブロックが実際に起動されるまで存在する必要があります。
- このハンドルのメモリ管理はそれほど明白ではありません。ブロックがハンドルを解放すると、後でダングリングポインターを逆参照する可能性がありますが、解放すると、ブロックは後でそれを実行する可能性があります。
- したがって、所有権をオンデマンドで渡す必要があります。
- 2つのブロックがあります。1つはとにかく起動する制御ブロックで、もう1つはキャンセルされる可能性のあるペイロードです。
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()は、ハンドルの[潜在的に延期された]デストラクタとして機能します。
C所有者の例:
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の例:
- (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の内部構造と比較して、絶対にわずかです。これらの内部構造は、送信されるブロックの実際の配列を保持し、必要に応じてそれらをデキューします。
- shouldCallとshouldFreeが実際には同じ反転フラグであることに気付くかもしれません。ただし、所有者インスタンスは、「自己」または他の所有者関連データに依存しない場合、ペイロードブロックを実際にキャンセルせずに、所有権を渡し、さらには-[dealloc]自体を渡すことができます。これは、dispatch_cancel_h()への追加のshouldCallAnyway引数を使用して実装できます。
- 警告注:このソリューションにはdidXYZフラグの同期も欠けており、制御ブロックとキャンセルルーチンの間で競合が発生する可能性があります。OSAtomicOr32Barrier()&coを使用して同期します。