1

ユーザーが画面上の要素を操作すると、アプリが移動してデータベースにクエリを実行し、ユーザーが選択したものに基づいて注釈を追加するマップベースのプログラムがあります。マップ UI 全体がロックされるのを防ぐために、このクエリを配置し、バックグラウンド スレッドに注釈コードを追加しました。これは完全に機能します。

問題は、ユーザーがインターフェイスの別の要素をクリックすると、バックグラウンド スレッドが終了する前に新しいスレッドが起動され、同じことを実行することです。これ自体は問題ではありませんが、低速のデバイス (古い iPod や iphone 3gs など) では、最初のスレッドよりも前に 2 番目のスレッドが終了する可能性があるため、ユーザーは最後の対話に関連するビューを短時間取得します。 、ただし、最初のインタラクションの処理に時間がかかる場合は、最初のインタラクションの結果が表示されます.. (古典的な競合状態)。

そこで、私がやりたいことは、2 番目のやり取りを行い、既に実行中のバックグラウンド スレッドに、現在行っていることを基本的に終了して終了できることを通知することです。どうすればこれを行うことができますか?

4

3 に答える 3

2

GCD ディスパッチ キューを使用します。FIFO の順序付けとブロックが自動的に提供されます。すでに実行中の要求をキャンセルすることはできませんが、他の実行を阻止することはできます。

CoreData を使用しているとは言わず、すでに別のスレッドを使用しているため、「データベース」として CoreData を使用していないと仮定します。

次のような簡単なことを試してください...

クラスが初期化されたときにキューを作成します(または、常にそこにあると想定されている場合はアプリが起動します)...

dispatch_queue_t workQ = dispatch_queue_create("label for your queue", 0);

クラスが解放されたとき(または他の適切な時間)に解放します...

dispatch_release(workQ);

キャンセルのようなものを取得するには、別の選択が行われた場合、トークンを使用して、作業中のリクエストが「最新」であるかどうか、またはそれ以降に別のリクエストが来ているかどうかを確認するなど、簡単なことを行うことができます...

static unsigned theWorkToken;
unsigned currentWorkToken = ++theWorkToken;
dispatch_async(workQ, ^{
    // Check the current work token at each step, to see if we should abort...
    if (currentWorkToken != theWorkToken) return;
    MyData *data = queryTheDatabaseForData(someQueryCriteria);

    if (currentWorkToken != theWorkToken) return;
    [data doTimeConsumingProcessing];

    for (Foo *foo in [data foo]) {
        if (currentWorkToken != theWorkToken) return;
        // Process some foo object
    }

    // Now, when ready to interact with the UI...
    if (currentWorkToken != theWorkToken) return;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Now you are running in the main thread... do anything you want with the GUI.
    });
});

ブロックは、スタック変数「currentWorkToken」とその現在の値を「キャプチャ」します。設定してから変更された場合にのみ、連続カウントを追跡する必要がないため、ここではロック解除チェックで問題ないことに注意してください。最悪の場合、余分な手順を実行します。

CoreData を使用している場合は、NSPrivateQueueConcurrencyType を使用して MOC を作成できます。プライベート MOC には独自の...

static unsigned theWorkToken;
unsigned currentWorkToken = ++theWorkToken;
[managedObjectContext performBlock:^{
    // Check the current work token at each step, to see if we should abort...
    if (currentWorkToken != theWorkToken) return;
    MyData *data = queryTheDatabaseForData(someQueryCriteria);

    if (currentWorkToken != theWorkToken) return;
    [data doTimeConsumingProcessing];

    for (Foo *foo in [data foo]) {
        if (currentWorkToken != theWorkToken) return;
        // Process some foo object
    }

    // Now, when ready to interact with the UI...
    if (currentWorkToken != theWorkToken) return;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Now you are running in the main thread... do anything you want with the GUI.
    });
}];

GCD/Blocks は、この種のものに適した方法です。

編集

なぜそれが好まれるのですか?まず、ブロックを使用すると、別のクラス (NSOperation) の他のメソッドに広がる代わりに、コードをローカライズしたままにすることができます。他にもたくさんの理由がありますが、個人的な好みについて話しているわけではないので、個人的な理由は脇に置いておきます。私はAppleのことを話していました。

ある週末、WWDC 2011 のすべてのビデオをご覧ください。さあ、それは本当に爆発です。私はそれを心から意味します。あなたがアメリカにいるなら、長い週末が近づいています。もっと良いことを考えられないに違いない...

とにかく、これらのビデオを見て、さまざまなプレゼンターが GCD とブロックの使用を強く推奨すると述べた回数を数えることができるかどうかを確認してください。

さて、WWDC 2012 ですぐにすべてが変わるかもしれませんが、私はそれを疑っています。

具体的には、この場合、NSOperation よりも優れたソリューションでもあります。はい、まだ開始していない NSOperation をキャンセルできます。ウーピー。NSOperation は、既に実行を開始した操作を自動的にキャンセルする方法をまだ提供していません。isCanceled をチェックし続け、キャンセル要求が行われたときに中止する必要があります。そのため、if (currentToken != theWorkToken) はすべて if ([self isCancelled]) のように存在する必要があります。はい、後者の方が読みやすいですが、未処理の操作を明示的にキャンセルする必要もあります。

私にとって、GCD/blocks ソリューションは、従うのがはるかに簡単で、ローカライズされており、(提示されているように) 暗黙的なキャンセル セマンティクスを備えています。NSOperation の唯一の利点は、開始前にキャンセルされた場合、キューに入れられた操作が自動的に実行されなくなることです。ただし、独自の実行中の操作をキャンセルする機能を提供する必要があるため、NSOperation には利点がありません。

NSOperation には適切な場所があり、単純な GDC よりも優先するいくつかの場所をすぐに思いつくことができますが、ほとんどの場合、特にこの場合は適切ではありません。

于 2012-05-22T16:16:25.130 に答える
1

バックグラウンド アクティビティを操作 (バックグラウンド スレッドで実行される) に入れ、アクティブな操作の配列を保持し、ユーザーがマップにヒットしたときに配列をスキャンして、同じタスクが既に進行中であるかどうかを確認します。その場合は、新しい操作を開始しないか、現在の操作をキャンセルしてください。操作コードでは、isCancelled を定期的にチェックする必要があります。

于 2012-05-22T14:24:04.063 に答える
-1

-[NSOperationQueue cancelAllOperations] は -[NSOperation cancel] メソッドを呼び出します。これにより、後続の -[NSOperation isCancelled] の呼び出しは YES を返します。ただし、これを無効にするために 2 つのことを行いました。

@synthesize isCancelled を使用して、NSOperation の -isCancelled メソッドをオーバーライドしています。これを行う理由はありません。NSOperation はすでに完全に受け入れられる方法で -isCancelled を実装しています。

操作がキャンセルされたかどうかを判断するために、独自の _isCancelled インスタンス変数をチェックしています。NSOperation は、操作がキャンセルされた場合に [self isCancelled] が YES を返すことを保証します。カスタム セッター メソッドが呼び出されることも、独自のインスタンス変数が最新であることも保証されません。[self isCancelled] を確認する必要があります。

// MyOperation.h
@interface MyOperation : NSOperation {
}
@end

そして実装:

// MyOperation.m
@implementation MyOperation

- (void)main {
    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }
    sleep(1);

    if ([self isCancelled]) {
        NSLog(@"** operation cancelled **");
    }

    // If you need to update some UI when the operation is complete, do this:
    [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO];

    NSLog(@"Operation finished");
}

- (void)updateButton {
    // Update the button here
}
@end

isExecuting、isCancelled、または isFinished では何もする必要がないことに注意してください。これらはすべて自動的に処理されます。-main メソッドをオーバーライドするだけです。それはとても簡単です。

次に、 MyOperation クラスからオブジェクトを作成し、別の操作がある場合は操作をキューに作成します..操作が既に実行されているかどうかを確認できます-?? それらをキャンセルして新しい操作を行った場合..

このリファレンスを読むことができます

于 2012-05-22T15:11:10.220 に答える