【问题标题】:Trap SIGINT in Cocoa macos application在 Cocoa macos 应用程序中捕获 SIGINT
【发布时间】:2018-05-08 03:51:11
【问题描述】:

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

func applicationWillTerminate(_ aNotification: Notification) {

}

然而,一个 Ctrl + C,SIGINT,永远不会在这里被抓住。在互联网上阅读,表明此功能不能保证执行,特别是如果应用程序在后台运行。

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

【问题讨论】:

  • 由于 SIGINT,没有调用应用委托方法。
  • @rmaddy 那么可以做些什么来诱捕他们呢?是否根本不可能在 ui 应用程序中捕获信号?
  • 对了,你为什么要处理SIGINT?您希望在什么情况下提高这一点? Mac GUI 应用程序通常不会收到这种情况。
  • 您可以使用“调度源”,参见Trapping signals in a Swift command line application
  • @MartinR,我已经更新了我对地址调度源的回答。问题是,如果SIGINT 旨在中断可能失控的进程,我不确定可以依赖调度源来触发。

标签: swift macos


【解决方案1】:

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

Technical Note TN2050: Observing Process Lifetimes Without Polling 是一个不同的主题,但说明了该技术。在那里,Apple 以这种方式描述了 Charles 的警告:

由于古怪的执行,监听信号可能会很棘手 与信号处理程序相关的环境。具体来说,如果你 安装一个信号处理程序(使用signalsigaction),你必须非常 小心你在那个处理程序中所做的事情。很少有功能是安全的 从信号处理程序调用。例如,分配是不安全的 内存使用malloc

对信号处理程序安全的函数(异步信号 安全函数)列在sigaction man page

在大多数情况下,您必须采取额外的步骤来重定向传入信号 到一个更明智的环境。

我从那里获取了代码插图并对其进行了修改以处理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 队列,但我不完全确定如果该进程已经有大量失控线程正在运行,该处理程序是否会运行。也就是说,实际上在高优先级队列上运行的任务将优先于低优先级线程或队列,但启动新任务可能不会。我不确定。

【讨论】:

  • 关于 GCD:我相信this old libdispatch-dev message 中的信息仍然是真实的。所以如果你创建了一个串行队列来处理调度源,并且你没有设置队列的目标,内核会在信号传递时根据需要启动一个新线程。
【解决方案2】:

您可以通过sigaction() 函数执行此操作:

import Foundation

let handler: @convention(c) (Int32) -> () = { sig in
    // handle the signal somehow
}

var action = sigaction(__sigaction_u: unsafeBitCast(handler, to: __sigaction_u.self),
                        sa_mask: 0,
                        sa_flags: 0)

sigaction(SIGINT, &action, nil)

确保只从处理程序中调用可重入函数。

【讨论】:

【解决方案3】:

虽然我不敢与上面的好答案竞争,但我认为有一些重要的事情要说,可能会大大简化解决方案。

我认为您的问题混合了两个非常不同级别的 MacOS 程序生命周期管理。

的确,您可以在 Mac 上运行简单的 posix 风格的可执行文件 - 但 Mac Cocoa 应用程序不是带有“附加 UI”的 posix 应用程序。 Mac 应用程序依赖于一个非常冗长、完整且功能丰富的生命周期机制,它比 posix 进程所能提供的更多——从状态保存和恢复(当被杀死/重新启动时)到自动启动以响应打开一个文件。能源管理、处理计算机睡眠、后台和前台转换、附加到附近设备上的同一个应用程序、模式等。

此外——我看不出人们怎么能期望键盘上的“ctrl-C”到达 Cocoa(“UI”)应用程序,因为在这种情况下键盘快捷键完全不同,你可以' t 从终端/shell 同步运行“UI”应用程序。

现在“中断”一个“卡住”或执行某项操作需要很长时间的 Cocoa 应用程序的正确方法是使用约定 command-preiod 组合键,许多人仍然广泛实施应用程序(Photoshop、视频编辑器、Finder 等)很抱歉,我无法在 Apple 用户界面指南中找到此定义 - 也许它不再是标准的一部分。但是,Ctrl-C 肯定不是!!!

您可以在您的应用中实现 Cmd-Period (⌘.)(即注册此高级“中断”操作,并优雅地处理它。

包装 Cocoa 应用程序的 NSApplication 对象忽略 SIGINT 是有充分理由的!完全断章取义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-09
    • 2010-12-27
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    • 2011-02-02
    • 2022-09-23
    • 1970-01-01
    相关资源
    最近更新 更多