Cela semble difficile à faire en Python, mais voici une solution unique. Il repose sur la génération de threads supplémentaires, mais n'utilise pas l'interrogation ET garantit que le thread d'origine est éveillé dès l'expiration du délai d'attente ou dès que wait() d'origine revient.
Remarque: Le code suivant inclut un scénario de test qui teste à la fois la fin de l'attente conditionnelle due à un dépassement de délai et une notification.
from thread import start_new_thread
from threading import Condition, Timer
class ConditionWithoutPolling():
"""Implements wait() with a timeout without polling. Wraps the Condition
class."""
def __init__(self, condition):
self.condition = condition
self.wait_timeout_condition = Condition()
def wait(self, timeout=None):
"""Same as Condition.wait() but it does not use a poll-and-sleep method
to implement timeouts. Instead, if a timeout is requested two new
threads are spawned to implement a non-pol-and-wait method."""
if timeout is None:
# just use the original implementation if no waiting is involved
self.condition.wait()
return
else:
# this new boolean will tell us whether we are done waiting or not
done = [False]
# wait on the original condition in a new thread
start_new_thread(self.wait_on_original, (done,))
# wait for a timeout (without polling) in a new thread
Timer(timeout, lambda : self.wait_timed_out(done)).start()
# wait for EITHER of the previous threads to stop waiting
with self.wait_timeout_condition:
while not done[0]:
self.wait_timeout_condition.wait()
def wait_on_original(self, done):
"""Waits on the original Condition and signals wait_is_over when done."""
self.condition.wait()
self.wait_is_over(done)
def wait_timed_out(self, done):
"""Called when the timeout time is reached."""
# we must re-acquire the lock we were waiting on before we can return
self.condition.acquire()
self.wait_is_over(done)
def wait_is_over(self, done):
"""Modifies done to indicate that the wait is over."""
done[0] = True
with self.wait_timeout_condition:
self.wait_timeout_condition.notify()
# wrap Condition methods since it wouldn't let us subclass it ...
def acquire(self, *args):
self.condition.acquire(*args)
def release(self):
self.condition.release()
def notify(self):
self.condition.notify()
def notify_all(self):
self.condition.notify_all()
def notifyAll(self):
self.condition.notifyAll()
def test(wait_timeout, wait_sec_before_notification):
import time
from threading import Lock
lock = Lock()
cwp = ConditionWithoutPolling(Condition(lock))
start = time.time()
def t1():
with lock:
print 't1 has the lock, will wait up to %f sec' % (wait_timeout,)
cwp.wait(wait_timeout)
time_elapsed = time.time() - start
print 't1: alive after %f sec' % (time_elapsed,)
# this thread will acquire the lock and then conditionally wait for up to
# timeout seconds and then print a message
start_new_thread(t1,())
# wait until it is time to send the notification and then send it
print 'main thread sleeping (will notify in %f sec)' % (wait_sec_before_notification,)
time.sleep(wait_sec_before_notification)
with lock:
cwp.notifyAll()
print 'notification sent, will continue in 2sec'
time.sleep(2.0) # give the other time thread to finish before exiting
if __name__ == "__main__":
print 'test wait() ending before the timeout ...'
test(2.0, 1.0)
print '\ntest wait() ending due to the timeout ...'
test(2.0, 4.0)
Très intéressant; Je n'avais pas utilisé la fonctionnalité de timeout des variables de condition en Python, donc je ne me suis pas rendu compte qu'elles étaient implémentées avec cette approche de sondage et de sommeil. À partir de la liste de diffusion Python-Dev, il semble que quelqu'un d'autre a remarqué cela et travaille sur un correctif (mail ici: http://mail.python.org/pipermail/python-dev/2009-November/094323.html, corrigez ici : http://bugs.python.org/issue7316) mais il semble que ce soit pour python3 (et pas encore officiellement accepté - bien qu'il ait été travaillé aussi récemment que la semaine dernière). –