7

我有一个通过 Grand Central Dispatch (GCD) 执行某些进程的 Cocoa 应用程序 (Mac OS X SDK 10.7)。这些进程正在以我认为是线程安全的方式操作一些核心数据 NSManagedObjects(非基于文档)(创建一个新的 managedObjectContext 以在该线程中使用)。

我遇到的问题是当调度队列仍在运行时用户尝试退出应用程序。

NSApplication 委托在实际退出之前被调用。

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 

我收到错误“无法合并更改”。这在某种程度上是意料之中的,因为仍然有通过不同的 managedObjectContext 执行的操作。然后,我会看到使用核心数据应用程序生成的模板中的 NSAlert。

Threading Programming Guide中有一个名为“Be Aware of Thread Behaviors at Quit Time”的部分提到使用replyToApplicationShouldTerminate:方法。我在实现这一点时遇到了一些麻烦。

我希望我的应用程序完成对排队项目的处理,然后在不向用户显示错误消息的情况下终止。更新视图或使用工作表让用户知道应用程序正在执行某些操作并将在操作完成时终止也会很有帮助。

我将在哪里以及如何实现这种行为?

解决方案:所以我在这里遇到了一些不同的问题。

  1. 我有块正在访问核心数据,以dispatch_queue防止我的应用程序正常终止。

  2. 当我尝试向 dispatch_queue 添加新项目时,dispatch_queue 的新实例在新线程上启动。

我为解决这个问题所做的工作是NSNotificationCenter在我的AppDelegate(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender被调用的地方)中使用。在 Core Data 生成的模板代码中添加以下内容:

// 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):

- (void)awakeFromNib {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(terminateApplicationFromQueue:) name:@"TerminateApplicationFromQueue" object:nil];

    // Set initial state of struct that dispatch_queue checks to see if it should terminate the application.
    appTerminating.isAppTerminating = NO;
    appTerminating.isTerminatingNow = NO;
}

我还创建了一个struct可以检查用户是否想要终止应用程序的方法。(我在上面设置了结构的初始状态awakeFromNib)。struct在您的@synthesize陈述之后放置:

struct {
    bool isAppTerminating;
    bool isTerminatingNow;
} appTerminating;

dispatch_queue现在对于阻止应用程序正常终止的长期运行。当我最初创建这个dispatch_queue时,一个 for 循环用于添加需要更新的项目。执行此 for 循环后,我添加了另一个队列项,它将检查struct应用程序是否应该终止:

// 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);
    }
}
4

1 回答 1

0

我自己没有处理过这个问题,但从我阅读文档来看,看起来你应该做的是:

  1. 从. NSTerminateLater_ applicationShouldTerminate:这让系统知道您的应用程序尚未准备好终止但很快就会终止。
  2. 在您的调度队列中排队一个“最终”块。(您需要确保其他块在此之后没有入队。在执行完所有其他工作后,该块将运行。注意队列必须是串行的——不是并发队列之一)才能正常工作.) “final”块应该做[NSApp replyToApplicationShouldTerminate:YES];,这将完成正常的终止过程。

没有任何直接的方法可以确定 GCD 队列是否仍在工作。你唯一能做的(据我所知)来处理这个问题是将所有块放入一个调度组中,然后在组中等待applicationShouldTerminate:(使用dispatch_group_wait().

于 2012-04-09T23:55:35.743 回答