diff --git a/AUTHORS.md b/AUTHORS.md
index 167fd496c..6cfa216b1 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -66,10 +66,10 @@
- Ville M. Vainio ([@vivainio](https://github.com/vivainio))
- Virgil Dupras ([@hsoft](https://github.com/hsoft))
- Wenguang Yang ([@yagweb](https://github.com/yagweb))
-- William Sardar ([@williamsardar])(https://github.com/williamsardar)
+- William Sardar ([@williamsardar](https://github.com/williamsardar))
- Xavier Dupré ([@sdpython](https://github.com/sdpython))
- Zane Purvis ([@zanedp](https://github.com/zanedp))
-- ([@amos402]https://github.com/amos402)
+- ([@amos402](https://github.com/amos402))
- ([@bltribble](https://github.com/bltribble))
- ([@civilx64](https://github.com/civilx64))
- ([@GSPP](https://github.com/GSPP))
@@ -82,3 +82,4 @@
- ([@testrunner123](https://github.com/testrunner123))
- ([@DanBarzilian](https://github.com/DanBarzilian))
- ([@alxnull](https://github.com/alxnull))
+- ([@gpetrou](https://github.com/gpetrou))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 147a716b5..375071841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax
- Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]).
+- Add GetPythonThreadID and Interrupt methods in PythonEngine
### Changed
- Drop support for Python 2, 3.4, and 3.5
diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs
new file mode 100644
index 000000000..e075d1194
--- /dev/null
+++ b/src/embed_tests/TestInterrupt.cs
@@ -0,0 +1,63 @@
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+using NUnit.Framework;
+
+using Python.Runtime;
+
+namespace Python.EmbeddingTest
+{
+ public class TestInterrupt
+ {
+ private IntPtr _threadState;
+
+ [OneTimeSetUp]
+ public void SetUp()
+ {
+ PythonEngine.Initialize();
+ _threadState = PythonEngine.BeginAllowThreads();
+ }
+
+ [OneTimeTearDown]
+ public void Dispose()
+ {
+ PythonEngine.EndAllowThreads(_threadState);
+ PythonEngine.Shutdown();
+ }
+
+ [Test]
+ public void InterruptTest()
+ {
+ int runSimpleStringReturnValue = int.MinValue;
+ ulong pythonThreadID = ulong.MinValue;
+ Task.Factory.StartNew(() =>
+ {
+ using (Py.GIL())
+ {
+ pythonThreadID = PythonEngine.GetPythonThreadID();
+ runSimpleStringReturnValue = PythonEngine.RunSimpleString(@"
+import time
+
+while True:
+ time.sleep(0.2)");
+ }
+ });
+
+ Thread.Sleep(200);
+
+ Assert.AreNotEqual(ulong.MinValue, pythonThreadID);
+
+ using (Py.GIL())
+ {
+ int interruptReturnValue = PythonEngine.Interrupt(pythonThreadID);
+ Assert.AreEqual(1, interruptReturnValue);
+ }
+
+ Thread.Sleep(300);
+
+ Assert.AreEqual(-1, runSimpleStringReturnValue);
+ }
+ }
+}
diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs
index df6cf7101..781d345e7 100644
--- a/src/runtime/pythonengine.cs
+++ b/src/runtime/pythonengine.cs
@@ -567,6 +567,30 @@ public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = nu
}
}
+ ///
+ /// Gets the Python thread ID.
+ ///
+ /// The Python thread ID.
+ public static ulong GetPythonThreadID()
+ {
+ dynamic threading = Py.Import("threading");
+ return threading.InvokeMethod("get_ident");
+ }
+
+ ///
+ /// Interrupts the execution of a thread.
+ ///
+ /// The Python thread ID.
+ /// The number of thread states modified; this is normally one, but will be zero if the thread id isn’t found.
+ public static int Interrupt(ulong pythonThreadID)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Runtime.PyThreadState_SetAsyncExcLLP64((uint)pythonThreadID, Exceptions.KeyboardInterrupt);
+ }
+
+ return Runtime.PyThreadState_SetAsyncExcLP64(pythonThreadID, Exceptions.KeyboardInterrupt);
+ }
///
/// RunString Method. Function has been deprecated and will be removed.
diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs
index 2454c038f..8197f027b 100644
--- a/src/runtime/runtime.cs
+++ b/src/runtime/runtime.cs
@@ -2143,6 +2143,12 @@ internal static void Py_CLEAR(ref IntPtr ob)
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern int Py_AddPendingCall(IntPtr func, IntPtr arg);
+ [DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int PyThreadState_SetAsyncExcLLP64(uint id, IntPtr exc);
+
+ [DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int PyThreadState_SetAsyncExcLP64(ulong id, IntPtr exc);
+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
internal static extern int Py_MakePendingCalls();