You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was used to using the uasyncio implementation from micropython-lib. I saw you're moving to an implementation within extmod, so I thought I start using that instead. My issue is I don't know the proper way to cancel a task.
If I call cancel() on a uasyncio.Task instance, I get the following error:
I'm not trying to cancel a task from within itself, rather I'm calling task.cancel()
The code is:
importuasyncio_loop=uasyncio.get_event_loop()
_tasks= {}
defcreate_task(name, coro, *args):
ifstr(name) notin_tasks:
_tasks[name] =_loop.create_task(coro(*args))
returnTruereturnFalsedefdestroy_task(name):
ifstr(name) in_tasks:
_tasks[name].cancel() # this is the line raising the errordel(_tasks[name])
returnTruereturnFalse
Assume there's a task created with create_task('mytask', mycoro()) and I'm calling destroy_task('mytask') when the error is raised.
Sorry for not showing the whole test case. I have tried yours, which works, but the fact that it works, doesn't solve the error that I get. I will try to strip down my case to the relevant parts and share it. Hopefully I'll find out the cause of the error.
importuasyncio_loop=uasyncio.get_event_loop()
_tasks= {}
asyncdefdummy_task():
whileTrue:
print('dummy')
awaituasyncio.sleep(0)
defcreate_task(name, coro, *args):
ifstr(name) notin_tasks:
_tasks[name] =_loop.create_task(coro(*args))
returnTruereturnFalsedefdestroy_task(name):
ifstr(name) in_tasks:
_tasks[name].cancel() # this is the line raising the errordel(_tasks[name])
returnTruereturnFalsedefstop():
fortask_namein_tasks:
print('destroy', task_name)
destroy_task(task_name)
defrun():
create_task('dummy', dummy_task)
_loop.run_forever()
try:
run()
exceptKeyboardInterrupt:
stop()
(it will spam a lot of 'dummy' 😄 )
I suspect the problem is that the KeyboardInterrupt is raised within the coroutine itself, therefore it is considered to be still executing, and thus can not be cancelled. Is this a desired behavior?
I have not read up on how exceptions like this mesh with tasks. I also don't know what is still executing by the time you hit your call to stop()... Might want to read the spec... How does CPython behave?
This code doesn't even run on CPython correctly because stop() iterates over a dictionary whose size gets changed:
Traceback (most recent call last):
File "R:/test.py", line 43, in <module>
stop()
File "R:/test.py", line 30, in stop
for task_name in _tasks:
RuntimeError: dictionary changed size during iteration
But,I now know where the "problem" is.
uasyncio.cur_task is being set in
and when a task is cancelled, it checks if it is uasyncio.cur_task and if so, throws a runtime error. If you exit the loop by interrupt, that variable will still stay the same although no task is running anymore because uasyncio exited.
However, this is not a problem because what @antariandel is trying to do, doesn't even work on CPython. You simply can't cancel tasks after exiting the asyncio loop because the CancelledErrors won't be raised to the tasks anymore becauase asyncio is not running anymore.
See this minimal testcase and run it on Cpython, you'll see that the 2nd task will never catch any exception (or ever even see that it got cancelled):
try:
import asyncio as uasyncio
except:
import uasyncio
_loop = uasyncio.get_event_loop()
_tasks=[]
async def dummy_task():
while True:
try:
print('dummy')
await uasyncio.sleep(0)
except uasyncio.CancelledError as e:
print("Task cancelled",e)
raise
except Exception as e:
print("Task exception",e)
raise
except BaseException as e:
print("Task base exception",e)
raise
def create_task(name, coro, *args):
if str(name) not in _tasks:
_tasks[name] = _loop.create_task(coro(*args))
return True
return False
def run():
global _task
loop=uasyncio.get_event_loop()
_tasks.append(loop.create_task(dummy_task()))
_tasks.append(loop.create_task(dummy_task()))
try:
loop.run_forever()
except Exception as e:
print("Run exception",e)
raise
except BaseException as e:
print("Run base exception",e)
raise
def stop():
for t in _tasks:
t.cancel()
try:
run()
except KeyboardInterrupt:
stop()
It would possible to live without this, if there was a way to clean up the loop and start fresh. In CPython this isn't a problem, because you can have more loops, but in uasyncio, the event loop is a singleton.
Activity
[-]cancellation in extmod/uasyncio[/-][+]cancellation error in extmod/uasyncio[/+]tve commentedon Mar 27, 2020
The error says that a task cannot cancel itself.
If you think that that's not what your code is attempting, please post a test case.
nazsolti commentedon Mar 27, 2020
I'm not trying to cancel a task from within itself, rather I'm calling
task.cancel()The code is:
Assume there's a task created with
create_task('mytask', mycoro())and I'm callingdestroy_task('mytask')when the error is raised.tve commentedon Mar 27, 2020
In the future please post complete test cases that a reviewer can run!
Here's my test case:
Transcript:
Problem?
nazsolti commentedon Mar 27, 2020
Sorry for not showing the whole test case. I have tried yours, which works, but the fact that it works, doesn't solve the error that I get. I will try to strip down my case to the relevant parts and share it. Hopefully I'll find out the cause of the error.
nazsolti commentedon Mar 28, 2020
Try it with:
(it will spam a lot of 'dummy' 😄 )
I suspect the problem is that the KeyboardInterrupt is raised within the coroutine itself, therefore it is considered to be still executing, and thus can not be cancelled. Is this a desired behavior?
tve commentedon Mar 28, 2020
I have not read up on how exceptions like this mesh with tasks. I also don't know what is still executing by the time you hit your call to stop()... Might want to read the spec... How does CPython behave?
kevinkk525 commentedon Mar 28, 2020
This code doesn't even run on CPython correctly because stop() iterates over a dictionary whose size gets changed:
But,I now know where the "problem" is.
uasyncio.cur_task is being set in
micropython/extmod/uasyncio/core.py
Line 174 in 6c77d5c
and when a task is cancelled, it checks if it is uasyncio.cur_task and if so, throws a runtime error. If you exit the loop by interrupt, that variable will still stay the same although no task is running anymore because uasyncio exited.
However, this is not a problem because what @antariandel is trying to do, doesn't even work on CPython. You simply can't cancel tasks after exiting the asyncio loop because the CancelledErrors won't be raised to the tasks anymore becauase asyncio is not running anymore.
See this minimal testcase and run it on Cpython, you'll see that the 2nd task will never catch any exception (or ever even see that it got cancelled):
nazsolti commentedon Mar 28, 2020
It would possible to live without this, if there was a way to clean up the loop and start fresh. In CPython this isn't a problem, because you can have more loops, but in uasyncio, the event loop is a singleton.
dpgeorge commentedon Mar 28, 2020
To clean up the event loop would need at least one of the following:
run_until_completereturns make sure it clears the task and IO queuesLoop.close()method which clears the task and IO queues@antariandel you could try the following to see if it works for you:
nazsolti commentedon Apr 3, 2020
@dpgeorge thanks, this works for me!
andrewleech commentedon Apr 7, 2020
@dpgeorge I see
uasyncio.core.Loop.close()is currently just apass.Should your suggested close function above be added there?
dpgeorge commentedon Apr 7, 2020
Yes that's probably a good solution for now.
peterhinch commentedon Apr 7, 2020
This sounds like a very worthwhile improvement.
dpgeorge commentedon Apr 9, 2020
The functionality to reset the event loop is added in PR #5890 (using
new_event_loop()rather thanclose()).Merge pull request micropython#5820 from CharString/waveshare_rp2040_…