【问题标题】:Graceful termination of NSApplication with Core Data and Grand Central Dispatch (GCD)使用 Core Data 和 Grand Central Dispatch (GCD) 优雅终止 NSApplication
【发布时间】:2019-03-30 19:49:42
【问题描述】:

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

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

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

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 

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

Threading Programming Guide 中有一个名为“在退出时注意线程行为”的部分,暗示使用replyToApplicationShouldTerminate: 方法。我在实现这一点时遇到了一些麻烦。

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

我将在何处以及如何实现此行为?

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

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

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

我解决这个问题的方法是在我的AppDelegate 中使用NSNotificationCenter(其中(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);
    }
}

【问题讨论】:

  • 澄清一下:您是从applicationShouldTerminate: 返回NSTerminateLater,如replyToApplicationShouldTerminate: 文档中所述?
  • 不,我不是。我不知道在哪里添加这个。
  • 那你返回什么?
  • 现在它只是核心数据生成的默认样板。所以它会弹出一个警报,上面写着“退出时无法保存更改。还是退出?”如果用户选择“仍然退出”,则返回 NSTerminateNow,否则返回 NSTerminateCancel。
  • 如果您不能立即终止,您应该设置您需要的任何内部状态,以向应用程序的其他部分发出信号,告知它们需要关闭并返回NSTerminateLater。在模板代码中,您可以将其作为-applicationShouldTerminate: 中的唯一内容执行,或者在if (!__managedObjectContext) ... 部分之后执行。然后,当您的关闭进程完成后,您在主线程上调用 -replyToApplicationShouldTerminate:

标签: objective-c macos cocoa


【解决方案1】:

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

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

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

【讨论】:

  • 似乎已经完成了一半。出于某种原因,dispatch_queue 是并发运行的,而不是串行运行的。我尝试将队列创建为trackingQueue = dispatch_queue_create("com.example.addtracking", NULL);trackingQueue = dispatch_queue_create("com.example.addtracking", DISPATCH_QUEUE_SERIAL); 我也在创建一个dispatch_group:refreshGroup = dispatch_group_create(); 正在将项目添加到调度组:dispatch_group_async(refreshGroup, trackingQueue, ^{ NSLog(@"some action goes here"); });
  • 无论队列是串行的还是并发的,等待组都应该起作用(串行也是默认值)。是什么让你认为它是并发的?一旦你点击applicationShouldTerminate:,你确定没有其他东西被排队吗?
  • 我在每个块中都有一些调试内容记录到控制台,以便查看发生了什么。当我将终止块添加到队列中时,该块在队列中的其他项目执行之前执行。该应用程序现在似乎正在无错误地终止,但我不想在确定事情按预期工作之前提交更新。至于在我提交applicationShouldTerminate 后添加到队列中的项目,我还没有创建机制来防止这种情况发生。我想我可以创建一个结构来处理它。
  • 我只是想到了应用程序立即终止的一个可能原因。我的dispatch_queue 是在我的AppController 中创建的,而applicationShouldTerminate 方法是在AppDelegate 中。当我将终止块添加到队列中时,我在AppController 上创建了一个类方法,并在我的AppDelegate 中调用了它。队列是否被视为常规的 Objective-C 对象?这意味着,虽然我将一个项目添加到同一个队列中,但我实际上是将终止块添加到队列的新实例中?
  • 如果你在两个地方调用dispatch_queue_create(),那么,是的,你有两个不同的队列。不幸的是,描述性字符串(第一个参数)对系统没有任何实际意义——也就是说,它的意思似乎是“如果这个名称已经用于队列,那么给我那个,否则创建一个新的”。我想说最好的办法是让代理向拥有队列的控制器发送一条消息——[appController finalizeThatQueue];——虽然是的,你可以像任何对象一样传递队列本身。跨度>
猜你喜欢
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 2011-07-28
  • 2012-08-10
  • 2012-05-09
相关资源
最近更新 更多