一尘不染

在可可macOS应用程序中陷阱SIGINT

swift

我正在尝试为针对MacOS制作的UI应用程序捕获SIGINT。在应用程序委托类中,我看到以下方法:

func applicationWillTerminate(_ aNotification: Notification) {

}

但是,Ctrl+ C,SIGINT,永远不会陷入这里。在互联网上阅读表明,不能保证执行此功能,尤其是在应用程序在后台运行时。

我可以在应用程序委托中做什么以捕获SIGINT?还是我还有其他地方可以捕获中断,以便可以适当地关闭资源?


阅读 273

收藏
2020-07-07

共1个答案

一尘不染

Charles的回答是正确的,但是他的警告(“确保仅从处理程序中调用可重入函数”)是一个极端的限制。使用kqueue和可以将信号处理重定向到更安全的环境CFFileDescriptor

TN2050技术说明:没有轮询的情况下观察过程寿命是另一个主题,但它说明了该技术。苹果在那里以这种方式描述了查尔斯的警告:

监听信号可能很棘手,因为与信号处理程序关联的执行环境很古怪。具体来说,如果您安装了信号处理程序(使用signalsigaction),则必须非常小心在该处理程序中执行的操作。从信号处理程序调用的函数很少。例如,使用malloc!分配内存是不安全的。

sigaction手册页上列出了可从信号处理程序中 安全使用的 功能(
异步信号安全 功能)。

在大多数情况下,您必须采取额外的步骤将传入的信号重定向到更合理的环境。

我从那里获取了代码说明,并对其进行了修改以进行处理SIGINT。抱歉,是Objective-C。这是一次性设置代码:

// Ignore SIGINT so it doesn't terminate the process.

signal(SIGINT, SIG_IGN);

// Create the kqueue and set it up to watch for SIGINT. Use the 
// EV_RECEIPT flag to ensure that we get what we expect.

int kq = kqueue();

struct kevent changes;
EV_SET(&changes, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
(void) kevent(kq, &changes, 1, &changes, 1, NULL);

// Wrap the kqueue in a CFFileDescriptor. Then create a run-loop source
// from the CFFileDescriptor and add that to the runloop.

CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
CFFileDescriptorRef kqRef = CFFileDescriptorCreate(NULL, kq, true, sigint_handler, &context);
CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, kqRef, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);

CFFileDescriptorEnableCallBacks(kqRef, kCFFileDescriptorReadCallBack);
CFRelease(kqRef);

这是实现sigint_handler上面引用的回调的方法:

static void sigint_handler(CFFileDescriptorRef f,  CFOptionFlags callBackTypes, void *info)
{
    struct kevent event;

    (void) kevent(CFFileDescriptorGetNativeDescriptor(f), NULL, 0, &event, 1, NULL);
    CFFileDescriptorEnableCallBacks(f, kCFFileDescriptorReadCallBack);

    // You've been notified!
}

请注意,此技术要求您在有兴趣处理SIGINT(也许是应用程序的生命周期)并维护/运行其运行循环的线程上运行安装代码。系统出于自身目的创建的线程(例如,服务于Grand
Central Dispatch队列的线程) 适合此目的。

该应用程序的主线程将正常工作,您可以使用它。 但是
,如果主线程锁定或变得无响应,则它不为运行循环提供服务,并且SIGINT不会调用处理程序。由于SIGINT经常用于恰好中断这种卡住的过程,因此主线程可能不适合。

因此,您可能想产生一个自己的线程只是为了监视此信号。它不应该做任何其他事情,因为其他任何事情也可能导致它卡住。即使在那里,也存在问题。您的处理程序函数将在您的该后台线程上被调用,并且主线程可能仍被锁定。在系统库中,有很多东西仅是主线程的,您将无能为力。但是,与POSIX样式的信号处理程序相比,您将拥有更大的灵活性。

我应该补充一点,GCD的调度源也可以监视UNIX信号,并且更易于使用,尤其是从Swift中。但是,它们不会预先创建运行该处理程序的专用线程。该处理程序将被提交到队列。现在,您可以指定一个高优先级/高QOS队列,但是我不确定如果该进程有多个运行中的失控线程正在运行,则该处理程序将运行。也就是说,实际上在高优先级队列上运行的任务将优先于低优先级线程或队列,但是启动新任务可能不会。我不确定。

2020-07-07