ディスパッチキュー:走っているかどうかを見分ける方法と止める方法
-
20-09-2019 - |
質問
私はちょうど GCD をいじっていて、おもちゃの CoinFlipper アプリを作成しました。
コインを投げる方法は次のとおりです。
- (void)flipCoins:(NSUInteger)nFlips{
// Create the queues for work
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
// Split the number of flips into whole chunks of kChunkSize and the remainder.
NSUInteger numberOfWholeChunks = nFlips / kChunkSize;
NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize;
if (numberOfWholeChunks > 0) {
for (NSUInteger index = 0; index < numberOfWholeChunks; index++) {
dispatch_async(queue, ^{
NSUInteger h = 0;
NSUInteger t = 0;
flipTheCoins(kChunkSize, &h, &t);
dispatch_async(mainQueue, ^{
self.nHeads += h;
self.nTails += t;
});
});
}
}
if (numberOfRemainingFlips > 0) {
dispatch_async(queue, ^{
NSUInteger h = 0;
NSUInteger t = 0;
flipTheCoins(numberOfRemainingFlips, &h, &t);
dispatch_async(mainQueue, ^{
self.nHeads += h;
self.nTails += t;
});
});
}
}
ご覧のように;フリップの数を大きなチャンクに分割し、バックグラウンドでフリップし、メインキューのプロパティを更新しています。プロパティはウィンドウ コントローラーによって監視され、UI は実行結果で更新されます。
Concurrency Programming Guide と GCD docs を調べましたが、キューを一時停止する方法はありますが、キューを停止し、キューに入れられた実行されていないオブジェクトをすべて削除する方法はありません。
反転が開始された後に反転をキャンセルするために「停止」ボタンを接続できるようにしたいと考えています。と NSOperationQueue
観察できるのは、 operationCount
実行中かどうかを知るためのプロパティ、および cancelAllOperations
キューに入れられたブロックを削除します。
Concurrency Programming Guide と GCD docs を調べましたが、キューを一時停止する方法はありますが、キューを停止し、キューに入れられた実行されていないオブジェクトをすべて削除する方法はありません。
それで :-
- キューに追加したブロックがまだ待機中かどうかを確認するにはどうすればよいですか?
- まだ実行されていないブロックをキャンセルするにはどうすればよいですか?
- 私は GCD については初めてですが、正しくやっているでしょうか?
解決
これは、GCD を使用してプログラミングするときによくある質問です。
簡単に言うと、GCD にはキュー用のキャンセル API がありません。理論的根拠:
- 特定のブロックが特定のメモリ割り当ての free() を担当する可能性があるため、メモリ管理は非常に複雑になります。GCD は常にブロックを実行することで、メモリ管理を容易にします。
- 事態を止めることは事実上不可能です ランニング 状態を壊さずにブロックします。
- キャンセル ロジックを必要とするほとんどのコードは、すでにプライベート データ構造でその状態を追跡しています。
これらすべてのケースを考慮すると、次のようなコードを記述する方がはるかに効率的で強力です。
dispatch_async(my_obj->queue, ^{
bool done = false;
// do_full_update() takes too long, therefore:
while ( !my_obj->cancelled && !done ) {
done = do_partial_update(my_obj);
}
});
ああ、キューがエンキューされたすべてのブロックの実行を終了したかどうかを知るには、コードで空のブロックを実行するだけです。 同期 API:
dispatch_sync(my_obj->queue, ^{});
コメントで述べたように、作業がいつ完了したかを知るより良い方法は、ディスパッチ グループを使用することです。すべてのブロックをグループにディスパッチすると、完了ハンドラーをグループに追加できます。作業が完了すると、完了ブロックが実行されます。
dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_async(myGroup, my_obj->queue, ^{
bool done = false;
while ( !my_obj->cancelled && !done ) {
done = do_partial_update(my_obj);
}
});
dispatch_group_notify(myGroup, my_obj->queue, ^{
NSLog(@"Work is done!");
dispatch_release(myGroup);
});
すべてのブロックが完了すると、グループは空になり、通知ブロックがトリガーされます。そこから UI などを更新できます。
頑張って楽しんでね!
他のヒント
で実行されている場合伝えるためにどのように
BOOL dispatch_queue_is_empty(dispatch_queue_t queue)
{
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_group_leave(group);
});
int64_t maxWaitTime = 0.00000005 * NSEC_PER_SEC;
BOOL isReady = dispatch_group_wait(group, maxWaitTime) == 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
});
return isReady;
}
アプリでテストするには、
dispatch_queue_t queue = dispatch_queue_create("test", 0);
NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");
dispatch_async(queue, ^{
for(int i = 0; i < 100; i++)
{
NSLog(@"... %i", i);
}
});
NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");
結果
Is empty YES
Is empty NO
... 0
... 1
... 2
... 3
... 4
... 5
... 6
... 7
... 8
... 9
変数maxWaitTime
のデフォルト値は、希望の結果を微調整することができます。
は、ここで同じことを行うことができますコードです。
BOOL __block queueIsEmpty = false;
dispatch_barrier_async (_dispatchQueue, ^{
queueIsEmpty = true;
});
while (!queueIsEmpty) {
int i = 0; // NOOP instruction
}
// At this point your queue should be empty.