Description
Current Situation
Presently, the "cleanup" behavior for async effects is that they are cancelled before the next effect takes place or the component is unmounted. While this behavior may be desirable in many cases, it does not give the user the ability to gracefully deal with cleanup logic in the way one can with a sync event.
For example, consider a long running async effect:
@use_effect
async def long_running_task():
while True:
await something()
await something_else()
The problem with this current behavior is that handling the cancellation is messy. You could do the following:
@use_effect
async def long_running_task():
try:
while True:
await something()
await something_else()
finally:
... # cleanup logic here
However, this may lead to some confusing behavior since it's not possible to know whether something()
or something_else()
will receive the cancellation. You might have to do a lot of extra work to figure out what state your program was left in after the cancellation.
Proposed Actions
Thankfully the above only impacts async effects. With a synchronous event, you would be able to handle async task cleanup using something like the following:
@use_effect
def start_long_running_task():
interupt = Event()
asyncio.create_task(long_running_task(interupt))
return cleanup.set
async def long_running_task(interrupt):
while not interupt.is_set():
await something()
await something_else()
... # cleanup logic here
Given this, our proposed solution to the problem is to allow async effects to accept a similar interupt asyncio.Event
:
@use_effect
async def long_running_task(interrupt):
while not interupt.is_set():
await something()
await something_else()
... # cleanup logic here