class CliEventLoopManager(object): """Manage the main event loop using .NET threads. For the time being, this implementation is pretty light and mostly relies on .NET async doing "the right thing(tm)" with a sprinkle of threading here and there. """ def __init__(self): self._init_cancellation() self._disconnect_event = ManualResetEventSlim(False) def _init_cancellation(self): """Initialize the cancellation source and token.""" self.cancellation_token_source = CancellationTokenSource() self.cancellation_token = self.cancellation_token_source.Token self.cancellation_token.Register( lambda: LOGGER.debug('Started token cancelation')) def run_forever(self): """Kick-starts a blocking loop while the ROS client is connected.""" self._disconnect_event.Wait(self.cancellation_token) LOGGER.debug('Received disconnect event on main loop') def call_later(self, delay, callback): """Call the given function after a certain period of time has passed. Args: delay (:obj:`int`): Number of seconds to wait before invoking the callback. callback (:obj:`callable`): Callable function to be invoked when the delay has elapsed. """ # NOTE: Maybe there's a more elegant way of doing this def closure(): Thread.Sleep(delay * 1000) callback() Task.Factory.StartNew(closure, self.cancellation_token) def call_in_thread(self, callback): """Call the given function on a thread. Args: callback (:obj:`callable`): Callable function to be invoked in a thread. """ Task.Factory.StartNew(callback, self.cancellation_token) def terminate(self): """Signals the termination of the main event loop.""" self._disconnect_event.Set() if self.cancellation_token_source: self.cancellation_token_source.Cancel() # Renew to allow re-connects self._init_cancellation()
class CliEventLoopManager(object): """Manage the main event loop using .NET threads. For the time being, this implementation is pretty light and mostly relies on .NET async doing "the right thing(tm)" with a sprinkle of threading here and there. """ def __init__(self): self._init_cancellation() self._disconnect_event = ManualResetEventSlim(False) def _init_cancellation(self): """Initialize the cancellation source and token.""" self.cancellation_token_source = CancellationTokenSource() self.cancellation_token = self.cancellation_token_source.Token self.cancellation_token.Register( lambda: LOGGER.debug('Started token cancellation')) def run(self): """Kick-starts a non-blocking event loop. In this implementation, this is a no-op.""" pass def run_forever(self): """Kick-starts a blocking loop while the ROS client is connected.""" self._disconnect_event.Wait(self.cancellation_token) LOGGER.debug('Received disconnect event on main loop') def call_later(self, delay, callback): """Call the given function after a certain period of time has passed. Args: delay (:obj:`int`): Number of seconds to wait before invoking the callback. callback (:obj:`callable`): Callable function to be invoked when the delay has elapsed. """ # NOTE: Maybe there's a more elegant way of doing this def closure(): Thread.Sleep(delay * 1000) callback() Task.Factory.StartNew(closure, self.cancellation_token) def call_in_thread(self, callback): """Call the given function on a thread. Args: callback (:obj:`callable`): Callable function to be invoked in a thread. """ Task.Factory.StartNew(callback, self.cancellation_token) def blocking_call_from_thread(self, callback, timeout): """Call the given function from a thread, and wait for the result synchronously for as long as the timeout will allow. Args: callback: Callable function to be invoked from the thread. timeout (:obj: int): Number of seconds to wait for the response before raising an exception. Returns: The results from the callback, or a timeout exception. """ manual_event = ManualResetEventSlim(False) result_placeholder = {'manual_event': manual_event} ThreadPool.QueueUserWorkItem(WaitCallback(callback), result_placeholder) # Wait with timeout results bool indicating success/failure if timeout and manual_event.Wait(timeout * 1000, self.cancellation_token): return result_placeholder # Wait without timeouts raises in case of failure triggered by cancellation if not timeout: manual_event.Wait(self.cancellation_token) return result_placeholder self.raise_timeout_exception() def raise_timeout_exception(self, _result=None, _timeout=None): """Callback called on timeout. Args: _result: Unused--required by Twister. _timeout: Unused--required by Twister. Raises: An exception. """ raise Exception('No service response received') def get_inner_callback(self, result_placeholder): """Get the callback which, when called, provides result_placeholder with the result. Args: result_placeholder: (:obj: dict): Object in which to store the result. Returns: A callable which provides result_placeholder with the result in the case of success. """ def inner_callback(result): result_placeholder['result'] = result result_placeholder['manual_event'].Set() return inner_callback def get_inner_errback(self, result_placeholder): """Get the errback which, when called, provides result_placeholder with the error. Args: result_placeholder: (:obj: dict): Object in which to store the result. Returns: A callable which provides result_placeholder with the error in the case of failure. """ def inner_errback(error): result_placeholder['exception'] = error result_placeholder['manual_event'].Set() return inner_errback def terminate(self): """Signals the termination of the main event loop.""" self._disconnect_event.Set() if self.cancellation_token_source: self.cancellation_token_source.Cancel() # Renew to allow re-connects self._init_cancellation()