1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+ import asyncio
1516import inspect
1617import sys
1718import threading
@@ -52,11 +53,12 @@ def __del__(self):
5253 file = sys .stderr )
5354
5455 def __await__ (self ):
55- # Yield if the task is not finished
56- while not self ._done :
57- yield
56+ if not self ._done :
57+ yield self
5858 return self .result ()
5959
60+ __iter__ = __await__
61+
6062 def cancel (self ):
6163 """Request cancellation of the running task if it is not done already."""
6264 with self ._lock :
@@ -142,7 +144,7 @@ def _schedule_or_invoke_done_callbacks(self):
142144 if executor is not None :
143145 # Have the executor take care of the callbacks
144146 for callback in callbacks :
145- executor .create_task (callback , self )
147+ executor .call_soon (callback , self )
146148 else :
147149 # No executor, call right away
148150 for callback in callbacks :
@@ -176,7 +178,7 @@ def add_done_callback(self, callback):
176178 if self ._done :
177179 executor = self ._executor ()
178180 if executor is not None :
179- executor .create_task (callback , self )
181+ executor .call_soon (callback , self )
180182 else :
181183 invoke = True
182184 else :
@@ -199,6 +201,8 @@ class Task(Future):
199201
200202 def __init__ (self , handler , args = None , kwargs = None , executor = None ):
201203 super ().__init__ (executor = executor )
204+ if executor is None :
205+ raise RuntimeError ('Task requires an executor' )
202206 # _handler is either a normal function or a coroutine
203207 self ._handler = handler
204208 # Arguments passed into the function
@@ -212,62 +216,66 @@ def __init__(self, handler, args=None, kwargs=None, executor=None):
212216 self ._handler = handler (* args , ** kwargs )
213217 self ._args = None
214218 self ._kwargs = None
215- # True while the task is being executed
216- self ._executing = False
217- # Lock acquired to prevent task from executing in parallel with itself
218- self ._task_lock = threading .Lock ()
219219
220- def __call__ (self ):
220+ def __call__ (self , future = None ):
221221 """
222222 Run or resume a task.
223223
224224 This attempts to execute a handler. If the handler is a coroutine it will attempt to
225225 await it. If there are done callbacks it will schedule them with the executor.
226226
227227 The return value of the handler is stored as the task result.
228+
229+ This function must not be called in parallel with itself.
230+
231+ :param future: do not use
228232 """
229- if self ._done or self . _executing or not self . _task_lock . acquire ( blocking = False ) :
233+ if self ._done :
230234 return
231- try :
232- if self ._done :
233- return
234- self ._executing = True
235-
236- if inspect .iscoroutine (self ._handler ):
237- # Execute a coroutine
238- try :
239- self ._handler .send (None )
240- except StopIteration as e :
241- # The coroutine finished; store the result
242- self ._handler .close ()
243- self .set_result (e .value )
244- self ._complete_task ()
245- except Exception as e :
246- self .set_exception (e )
247- self ._complete_task ()
248- else :
249- # Execute a normal function
250- try :
251- self .set_result (self ._handler (* self ._args , ** self ._kwargs ))
252- except Exception as e :
253- self .set_exception (e )
235+ if inspect .iscoroutine (self ._handler ):
236+ # Execute a coroutine
237+ try :
238+ result = self ._handler .send (None )
239+ if isinstance (result , Future ):
240+ # Wait for an rclpy future to complete
241+ result .add_done_callback (self )
242+ elif asyncio .isfuture (result ):
243+ # Get the event loop of this thread (raises RuntimeError if there isn't one)
244+ event_loop = asyncio .get_running_loop ()
245+ # Make sure we're in the same thread as the future's event loop.
246+ # TODO(sloretz) is asyncio.Future.get_loop() thread-safe?
247+ if result .get_loop () is not event_loop :
248+ raise RuntimeError ('Cannot await asyncio future from a different thread' )
249+ # Resume this task when the asyncio future completes
250+ result .add_done_callback (lambda _ : self ._executor ().call_soon (self ))
251+ elif result is None :
252+ # Wait for one iteration if a bare yield is used
253+ self ._executor ().call_soon (self )
254+ else :
255+ # What is this intermediate value?
256+ # Could be a different async library's coroutine
257+ # Could be a generator yielded a value
258+ raise RuntimeError (f'Coroutine yielded unexpected value: { result } ' )
259+ except StopIteration as e :
260+ # Coroutine or generator returning a result
261+ self ._handler .close ()
262+ self .set_result (e .value )
254263 self ._complete_task ()
255-
256- self ._executing = False
257- finally :
258- self ._task_lock .release ()
264+ except Exception as e :
265+ # Coroutine or generator raising an exception
266+ self ._handler .close ()
267+ self .set_exception (e )
268+ self ._complete_task ()
269+ else :
270+ # Execute a normal function
271+ try :
272+ self .set_result (self ._handler (* self ._args , ** self ._kwargs ))
273+ except Exception as e :
274+ self .set_exception (e )
275+ self ._complete_task ()
259276
260277 def _complete_task (self ):
261278 """Cleanup after task finished."""
262279 self ._handler = None
263280 self ._args = None
264281 self ._kwargs = None
265-
266- def executing (self ):
267- """
268- Check if the task is currently being executed.
269-
270- :return: True if the task is currently executing.
271- :rtype: bool
272- """
273- return self ._executing
0 commit comments