C'est généralement une mauvaise habitude de tuer un fil de discussion de manière abrupte, en Python et dans n'importe quel langage. Pensez aux cas suivants :
- le fil retient une ressource critique qui doit être fermée correctement
- ce fil a créé plusieurs autres fils qui doivent être tués aussi.
La meilleure façon de gérer ce problème si vous pouvez vous le permettre (si vous gérez vos propres threads) est d'avoir un drapeau exit_request que chaque thread vérifie à intervalles réguliers pour voir s'il est temps pour lui de se retirer.
Par exemple :
import threading
class StoppableThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(self):
super(StoppableThread, self).__init__()
self._stop = threading.Event()
def stop(self):
self._stop.set()
def stopped(self):
return self._stop.isSet()
Dans ce code, vous devez appeler stop() sur le thread lorsque vous voulez qu'il se termine, et attendre que le thread se termine correctement en utilisant join(). Le thread doit vérifier le drapeau stop à intervalles réguliers.
Il y a cependant des cas où vous devez vraiment tuer un fil. Par exemple, lorsque vous enveloppez une bibliothèque externe qui est occupée par de longs appels et que vous voulez l'interrompre.
Le code suivant permet (avec quelques restrictions) de lever une exception dans un thread Python :
def _async_raise(tid, exctype):
'''Raises an exception in the threads with id tid'''
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:
# "if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect"
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
raise SystemError("PyThreadState_SetAsyncExc failed")
class ThreadWithExc(threading.Thread):
'''A thread class that supports raising exception in the thread from
another thread.
'''
def _get_my_tid(self):
"""determines this (self's) thread id
CAREFUL : this function is executed in the context of the caller
thread, to get the identity of the thread represented by this
instance.
"""
if not self.isAlive():
raise threading.ThreadError("the thread is not active")
# do we have it cached?
if hasattr(self, "_thread_id"):
return self._thread_id
# no, look for it in the _active dict
for tid, tobj in threading._active.items():
if tobj is self:
self._thread_id = tid
return tid
# TODO: in python 2.6, there's a simpler way to do : self.ident
raise AssertionError("could not determine the thread's id")
def raiseExc(self, exctype):
"""Raises the given exception type in the context of this thread.
If the thread is busy in a system call (time.sleep(),
socket.accept(), ...), the exception is simply ignored.
If you are sure that your exception should terminate the thread,
one way to ensure that it works is:
t = ThreadWithExc( ... )
...
t.raiseExc( SomeException )
while t.isAlive():
time.sleep( 0.1 )
t.raiseExc( SomeException )
If the exception is to be caught by the thread, you need a way to
check that your thread has caught it.
CAREFUL : this function is executed in the context of the
caller thread, to raise an excpetion in the context of the
thread represented by this instance.
"""
_async_raise( self._get_my_tid(), exctype )
Comme indiqué dans la documentation, il ne s'agit pas d'une solution miracle, car si le thread est occupé en dehors de l'interpréteur Python, il ne détectera pas l'interruption.
Un bon modèle d'utilisation de ce code est de faire en sorte que le thread attrape une exception spécifique et effectue le nettoyage. De cette façon, vous pouvez interrompre une tâche tout en assurant un nettoyage correct.