Objective c 使用核心数据和大中心调度(GCD)优雅地终止非应用程序
我有一个Cocoa应用程序(Mac OS X SDK 10.7),它通过Grand Central Dispatch(GCD)执行一些进程。这些进程正在以我认为是线程安全的方式操作一些核心数据NSManagedObject(非基于文档的)(创建一个新的ManagedObject上下文以在该线程中使用) 我遇到的问题是,当调度队列仍在运行时,用户试图退出应用程序 在实际退出之前正在调用NSApplication委托Objective c 使用核心数据和大中心调度(GCD)优雅地终止非应用程序,objective-c,macos,cocoa,Objective C,Macos,Cocoa,我有一个Cocoa应用程序(Mac OS X SDK 10.7),它通过Grand Central Dispatch(GCD)执行一些进程。这些进程正在以我认为是线程安全的方式操作一些核心数据NSManagedObject(非基于文档的)(创建一个新的ManagedObject上下文以在该线程中使用) 我遇到的问题是,当调度队列仍在运行时,用户试图退出应用程序 在实际退出之前正在调用NSApplication委托 - (NSApplicationTerminateReply)applicatio
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
我收到一个错误“无法合并更改”。这在某种程度上是意料之中的,因为仍有操作通过不同的managedObjectContext执行。然后,我将看到由核心数据应用程序生成的模板中的NSAlert
在中有一个名为“在退出时注意线程行为”的部分,其中暗指使用方法。我在实现这一点上有点困难
我希望我的应用程序完成对排队项目的处理,然后在不向用户显示错误消息的情况下终止。更新视图或使用工作表让用户知道应用程序正在执行某些操作,并在操作完成时终止,这也会很有帮助
我将在何处以及如何实施此行为
解决方案:
所以我这里有一些不同的问题
调度队列中有访问核心数据的块
阻止我的应用程序正常终止AppDelegate
中使用NSNotificationCenter
(其中(NapplicationTerminateReply)应用程序应终止:(Napplication*)发送方
。在核心数据生成的模板代码中添加以下内容:
// Customize this code block to include application-specific recovery steps.
if (error) {
// Do something here to add queue item in AppController
[[NSNotificationCenter defaultCenter] postNotificationName:@"TerminateApplicationFromQueue" object:self];
return NSTerminateLater;
}
然后在AppController
中添加通知的观察者(我将其添加到awakeFromNib
):
我还创建了一个struct
,可以检查用户是否想要终止应用程序。(我在上面的awakeFromNib
中设置了struct的初始状态)。将struct
放在@synthesis
语句之后:
struct {
bool isAppTerminating;
bool isTerminatingNow;
} appTerminating;
现在,对于长时间运行的dispatch\u queue
,它阻止应用程序正常终止。当我最初创建此dispatch\u queue
时,使用for循环添加需要更新的项。在执行此for循环后,我添加了另一个队列项,该队列项将检查结构
,以查看电子应用程序应终止:
// Additional queue item block to check if app should terminate and then update struct to terminate if required.
dispatch_group_async(refreshGroup, trackingQueue, ^{
NSLog(@"check if app should terminate");
if (appTerminating.isAppTerminating) {
NSLog(@"app is terminating");
appTerminating.isTerminatingNow = YES;
}
});
dispatch_release(refreshGroup);
以及在接收到通知时要调用的方法:
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(@"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}
我自己没有处理过这个问题,但从我阅读的文档来看,您应该做的是:
应用程序应终止:
返回NSTerminateLater
。这会让系统知道您的应用程序尚未准备好终止,但很快就会终止[NSApp replyToApplicationShouldTerminate:是];
,这将完成正常的终止过程没有任何直接的方法可以确定GCD队列是否仍在工作。要处理此问题,您唯一可以做的另一件事(据我所知)是将所有块放入一个队列中,然后在
应用程序中等待组houldTerminate:
(使用.只是为了澄清:您是否正在从应用程序返回nsterminater
,如回复应用程序应终止:
文档中所述?不,我不知道。我不知道该添加到哪里。那么,您返回的是什么?现在它只是核心数据生成的默认样板文件。所以如果用户选择“无论如何退出”,它会弹出一个警报,提示“退出时无法保存更改。无论如何退出?”NSTerminateNow返回,否则NSTerminateCancel返回。如果您不能立即终止,您应该设置您需要的任何内部状态,以向应用程序的其他部分发出信号,表明它们需要关闭并返回NSTerminateLater
。在模板代码中,您可以在-applicationshouldtimate:
或仅在if(!\uu managedObjectContext)之后执行此操作…
部分。然后,当关闭过程完成后,在主线程上调用-replyToApplicationShouldTerminate:
。似乎已经到了一半。出于某种原因,调度队列是并发运行的,而不是串行运行的。我已尝试将队列创建为跟踪队列=调度队列\u创建(“com.example.addtracking”,NULL);
和trackingQueue=dispatch\u queue\u create(“com.example.addtracking”,dispatch\u queue\u SERIAL”);
我还在创建一个调度组:refreshGroup=dispatch\u group\u create();
正在将项目添加到调度组:dispatch\u group\u async(refreshGroup,trackingQueue^{NSLog(@“some action goes here”);});
无论队列是串行队列还是并发队列,等待组都应该有效(串行队列也是默认队列)。是什么让你认为它是并发的?你确定在点击应用程序shouldtimiter:
后,没有其他任何东西正在排队吗?我将一些调试内容记录到每个块中的控制台,以便查看发生了什么。当我将terminate块添加到队列中时,该块在其他项之前执行
- (void)terminateApplicationFromQueue:(NSNotification *)notification {
// Struct to check against at end of dispatch_queue to see if it should shutdown.
if (!appTerminating.isAppTerminating) {
appTerminating.isAppTerminating = YES;
dispatch_queue_t terminateQueue = dispatch_queue_create("com.example.appname.terminate", DISPATCH_QUEUE_SERIAL); // or NULL
dispatch_group_t terminateGroup = dispatch_group_create();
dispatch_group_async(terminateGroup, terminateQueue, ^{
NSLog(@"termination queued until after operation is complete");
while (!appTerminating.isTerminatingNow) {
// add a little delay before checking termination status again
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"terminate now");
[NSApp replyToApplicationShouldTerminate:YES];
});
dispatch_release(terminateGroup);
}
}