python - Python 的键盘中断不会中止 Rust 功能(PyO3)

标签 python rust keyboardinterrupt pyo3

我有一个用 PyO3 用 Rust 编写的 Python 库,它涉及一些昂贵的计算(单个函数调用最多 10 分钟)。从 Python 调用时如何中止执行?

Ctrl+C 好像只在执行结束后才处理,所以本质上是没用的。

最小的可重现示例:

# Cargo.toml

[package]
name = "wait"
version = "0.0.0"
authors = []
edition = "2018"

[lib]
name = "wait"
crate-type = ["cdylib"]

[dependencies.pyo3]
version = "0.10.1"
features = ["extension-module"]

// src/lib.rs

use pyo3::wrap_pyfunction;

#[pyfunction]
pub fn sleep() {
    std::thread::sleep(std::time::Duration::from_millis(10000));
}

#[pymodule]
fn wait(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(sleep))
}

$ rustup override set nightly
$ cargo build --release
$ cp target/release/libwait.so wait.so
$ python3
>>> import wait
>>> wait.sleep()

输入 wait.sleep() 后立即我输入 Ctrl + C , 和字符 ^C打印到屏幕上,但仅 10 秒后我终于得到
>>> wait.sleep()
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt
>>>
KeyboardInterrupt被检测到,但直到调用 Rust 函数结束时才被处理。有没有办法绕过它?

当 Python 代码放入文件并从 REPL 外部执行时,行为是相同的。

最佳答案

您的问题与 this one 非常相似,除了你的代码是用 Rust 而不是 C++ 编写的。

您没有说您使用的是哪个平台 - 我将假设它类似于 unix。此答案的某些方面对于 Windows 可能不正确。

在类 unix 系统中,Ctrl+C 会导致 SIGINT信号被发送到您的进程。在 C 库的最低级别,应用程序可以注册函数,这些函数将在接收到这些信号时调用。见 man signal(7)有关信号的更详细说明。

因为可以随时调用信号处理程序(甚至在您通常认为是原子的某些操作的一部分中),所以信号处理程序实际上可以做的事情有很大的限制。这与编程语言或环境无关。大多数程序只是在收到信号然后返回时设置一个标志,然后检查该标志并对其采取行动。

Python 也不异常(exception)——它为 SIGINT 设置了一个信号处理程序。设置一些标志的信号,它会检查(在安全的情况下)并采取行动。

这在执行 python 代码时可以正常工作——它会在每个代码语句中至少检查一次标志——但是在执行一个用 Rust(或任何其他外语)编写的长时间运行的函数时,情况就不同了。在您的 rust 函数返回之前,不会检查该标志。

您可以通过检查 rust 函数中的标志来改进问题。 PyO3 exposes PyErr_CheckSignals正是这样做的功能。这个功能:

checks whether a signal has been sent to the processes and if so, invokes the corresponding signal handler. If the signal module is supported, this can invoke a signal handler written in Python. In all cases, the default effect for SIGINT is to raise the KeyboardInterrupt exception. If an exception is raised the error indicator is set and the function returns -1; otherwise the function returns 0



因此,您可以在 Rust 函数中以适当的时间间隔调用此函数,并检查返回值。如果是 -1,你应该立即从你的 Rust 函数返回;否则继续。

如果您的 Rust 代码是多线程的,情况会更加复杂。您只能调用PyErr_CheckSignals来自与调用你的 python 解释器相同的线程;如果它返回 -1,您将不得不清理您在返回之前启动的任何其他线程。究竟如何做到这一点超出了这个答案的范围。

关于python - Python 的键盘中断不会中止 Rust 功能(PyO3),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62364030/

相关文章:

reference - 借用的值在循环中的生命周期不够长

rust - 如何解决 [E0382] : use of moved value in a for loop?

c# - 如何在C#中实现Ctrl+C等关键中断的异常代码

python - PyScripter - 无法使用 KeyboardInterrupt 终止运行

python - 优化Python函数调用的性能

python - NumPy 数组下三角区域中 n 个最大值的索引

rust - 无法使用 Cargo 构建 Rust 教程中的 hello world 示例

python - DecimalField 将零转换为 0E-10

python - 在没有 root 访问权限的情况下安装 setuptools python 模块的简单方法?

python - 键盘中断与 python 的多处理池