На этот вопрос уже был дан ответ, и я пытаюсь реализовать второе решение в первый ответ описан здесь (примечание: первое решение меня не касается, мой поток запускает сервер из внешней библиотеки и не может быть изменен для проверки флага)

Я попытался реализовать простейший случай, который соответствует моим обстоятельствам. У меня есть класс, который порождает поток, и этот поток должен быть остановлен извне (поток никогда не завершается естественным образом, как в этом примере). Примечание: _async_raise и ThreadWithExc являются копиями / вставками принятого ответа на этот вопрос на SO :

import threading
import inspect
import ctypes
import time

# https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python
def _async_raise(tid, exctype):
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
                                                  ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

# https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python
class ThreadWithExc(threading.Thread):
    def _get_my_tid(self):

        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        if hasattr(self, "_thread_id"):
            return self._thread_id

        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
        _async_raise( self._get_my_tid(), exctype )

def work():
    while True:
        print('work')
        time.sleep(1)

class Server:
    def __init__(self):
        self.thread = ThreadWithExc(target=work)

    def start(self):
        self.thread.start()

    def stop(self):
        _async_raise(self.thread.raiseExc(TypeError))


server = Server()
server.start()
server.stop()

Это дает исключение ValueError: invalid thread id. Я также попытался threading.get_ident() вместо ответа _get_my_tid(); это дает мне другое удостоверение личности, но это также недействительно.

1
Juicy 29 Май 2017 в 01:35

2 ответа

Лучший ответ

Я думаю, что основная проблема у вас заключается в том, что вы не правильно вызываете _async_raise() и должны заменить строку:

_async_raise(self.thread.raiseExc(TypeError))

В Server.stop() с:

self.thread.raiseExc(TypeError)

Однако, если вы сделаете это, вы получите Exception in thread Thread-1:, потому что в функции work() нет обработчика исключений для обработки исключения, которое вызывается raiseExc().

Следующее исправляет это и использует пользовательский подкласс Exception, чтобы прояснить ситуацию:

import threading
import inspect
import ctypes
import time

# https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python
def _async_raise(tid, exctype):
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
                                                  ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

# https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread-in-python
class ThreadWithExc(threading.Thread):
    def _get_my_tid(self):

        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        if hasattr(self, "_thread_id"):
            return self._thread_id

        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
        _async_raise(self._get_my_tid(), exctype )

def work():
    try:
        while True:
            print('work')
            time.sleep(1)
    except Server.ThreadStopped:
        pass

    print('exiting work() function')

class Server:
    class ThreadStopped(Exception): pass

    def __init__(self):
        self.thread = ThreadWithExc(target=work)

    def start(self):
        self.thread.start()

    def stop(self):
#        _async_raise(self.thread.raiseExc(TypeError))
        self.thread.raiseExc(self.ThreadStopped)

server = Server()
server.start()
server.stop()

Выход:

work
exiting work() function
1
martineau 29 Май 2017 в 12:57

Python 3.6:

self._thread_id = ctypes.c_long(tid)

Python 3.7:

self._thread_id = ctypes.c_ulong(tid)
0
Hubert Tarnacki 2 Авг 2018 в 13:45