我有一个基于地图的程序,当用户与屏幕上的元素交互时,应用程序会查询数据库并根据用户选择的内容添加注释。为了防止整个地图用户界面被锁定,我已经把这个查询并在后台线程中添加注释代码..它完美地工作。
现在问题是,如果用户点击界面上的另一个元素,则在后台线程完成之前启动新线程以执行相同的操作。这本身并不是问题本身,但在较慢的设备(旧的iPod和iphone 3gs等)上,第二个线程可能在第一个线程完成之前完成,因此用户可以简单地获得与最后一个相关的视图交互,但是如果第一次交互需要很长时间来处理,则显示第一个的结果。(经典赛车条件)。
所以,我想做的是,进行第二次互动,告知已经在飞行中的任何后台线程嘿,你基本上可以放弃你现在正在做的事情并结束。我该怎么做呢?
答案 0 :(得分:2)
使用GCD调度队列。它自动提供FIFO排序和块。虽然您无法取消已在运行的请求,但可以阻止其他人运行。
由于您没有说您使用的是CoreData,并且您已经在使用单独的线程,因此我假设您并未使用CoreData作为“数据库”。
尝试像......这样简单的事情。
在您的课程初始化时创建您的队列(如果应用程序始终应该在那里,则启动应用程序)...
dispatch_queue_t workQ = dispatch_queue_create("label for your queue", 0);
并在你的课程deallocs(或其他适当的时间)释放它......
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)仍然必须在那里([self isCancelled])。是的,后者更容易阅读,但您还必须明确取消未完成的操作。
对我来说,GCD /块解决方案更容易遵循,本地化,并且(如所示)具有隐式取消语义。 NSOperation的唯一优势是,如果排队的操作在启动之前被取消,它将自动阻止排队操作的运行。但是,您仍然必须提供自己的取消运行操作功能,因此我认为NSOperation没有任何好处。
NSOperation有它的位置,我可以立即想到几个我赞成直接GDC的地方,但在大多数情况下,尤其是这种情况,这是不合适的。
答案 1 :(得分:1)
我会将后台活动放入一个操作(将在后台线程上运行),保留一个活动操作正在处理的数组,然后当用户点击地图时扫描数组以查看是否相同的任务是已经在进行中,如果是这样,要么不启动新操作,要么取消当前操作。在操作代码中,您需要定期检查isCancelled。
答案 2 :(得分:-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 cancellable