1

我有一个基于地图的程序,当用户与屏幕上的元素交互时,应用程序会查询数据库并根据用户选择的内容添加注释。为了防止整个地图 UI 锁定,我已将此查询并在后台线程中添加注释代码.. 它运行良好。

现在的问题是,如果用户点击界面上的另一个元素,在后台线程完成之前启动一个新线程来做同样的事情。这本身并不是问题,但在速度较慢的设备(旧 iPod 和 iphone 3gs 等)上,第二个线程可能在第一个线程之前完成,因此用户会短暂获得与最后一次交互相关的视图,但如果第一次交互需要很长时间来处理,则会显示第一个结果。(经典赛车条件)。

所以,我想做的是进行第二次交互,通知任何已经在运行的后台线程,嘿,你基本上可以退出你现在正在做的事情并结束。我该怎么做呢?

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)的其他方法中。还有很多其他的原因,但我将把我的个人原因放在一边,因为我不是在谈论我的个人喜好。我说的是苹果的。

只需坐一个周末,观看所有 WWDC 2011 视频。去吧,真的是大快人心。我是真诚的。如果您在美国,您将迎来一个长周末。我敢打赌,你想不出更好的办法……

不管怎样,看看这些视频,看看你能不能数出不同的演讲者说他们强烈推荐使用 GCD 的次数......和块......(以及仪器作为另一个)。

现在,WWDC 2012 可能很快就会改变,但我对此表示怀疑。

具体来说,在这种情况下,它也是比 NSOperation 更好的解决方案。是的,您可以取消尚未开始的 NSOperation。哎呀。NSOperation 仍然没有提供一种自动方式来取消已经开始执行的操作。您必须继续检查 isCanceled,并在发出取消请求时中止。因此,所有那些 if (currentToken != theWorkToken) 仍然必须像 ([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。但是,您已经做了两件事以使其无效。

您正在使用 @synthesize isCancelled 来覆盖 NSOperation 的 -isCancelled 方法。没有理由这样做。NSOperation 已经以完全可以接受的方式实现了 -isCancelled。

您正在检查自己的 _isCancelled 实例变量以确定操作是否已被取消。NSOperation 保证如果操作被取消, [self isCancelled] 将返回 YES。它不保证将调用您的自定义 setter 方法,也不保证您自己的实例变量是最新的。你应该检查 [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 类创建对象,并在有另一个操作时对队列进行操作..您可以检查您是否有操作已经在运行 -?? 如果您只是取消它们并进行新操作..

您可以阅读此参考Subclassing NSOperation to be concurrent and cancelable

于 2012-05-22T15:11:10.220 回答