Uh oh!
There was an error while loading. Please reload this page.
Better Async Effects - #1093
Conversation
This interface should be relatively similar to the Notes
importasynciofromreactpyimportcomponent, use_effect@componentdefexample():
asyncdefteardown():
...
@use_effect(timeout=2, teardown=teardown)asyncdefexample_effect(cancel: asyncio.Event):
whileTrue:
awaitsomething("my-message-type")
...
ifcancel.is_set():
break |
I think this is a slight simplification - teardown should just happen after the stop event is triggered. @use_effectasyncdefeffect(stop):
# do effectawaitstop.wait()
# cleanup
I thought about this for a bit and realized that a simple awaitasyncio.wait_for(task(), timeout...) |
Archmonger
commented
Jul 9, 2023
This can't always occur though. For example, in the case of the user closing their browser window. We need a timeout to cover edge cases like this.
We can call it
What you proposed isn't equivalent to a |
Both comments seem to address the fact that we might want to enforce a timeout when a connection is closed. That seems reasonable, but in that case, it seems better to introduce that timeout here when a Though, even without this, the user could enforce a "stop timeout" themselves if they wanted: awaitwait_for(create_effect(), timeout=...) # creation timeoutawaitstop.wait()
awaitwait_for(cleanup_effect(), timeout=...) # cleanup timeout |
f3d4405 to
be5cf27CompareSo thinking through our interface some more I'm realizing that: @use_effectasyncdefmy_effect(stop):
task=asyncio.create_task(do_something())
awaitstop.wait()
task.cancel()
awaitfinalize_it()Is not correct since simply cancelling a task does not mean that it will have exited by the time @use_effectasyncdefmy_effect(stop):
task=asyncio.create_task(do_something())
awaitstop.wait()
task.cancel()
try:
awaittaskexceptasyncio.CancelledError:
passawaitfinalize_it()This is far too complicated for users to understand. As such, I've done some thinking and realized that in 3.11 @use_effectasyncdefmy_effect(effect):
asyncwitheffect:
awaitdo_something()
awaitfinalize_it()The behavior is that awaitables within the The implementation of the classAsyncEffect:
_task: asyncio.Task|None=Nonedef__init__(self) ->None:
self._stop=asyncio.Event()
self._cancel_count=0defstop(self) ->None:
ifself._taskisnotNone:
self._cancel_task()
self._stop.set()
asyncdef__aenter__(self) ->None:
self._task=asyncio.current_task()
self._cancel_count=self._task.cancelling()
ifself._stop.is_set():
self._cancel_task()
returnNoneasyncdef__aexit__(self, exc_type: type[BaseException], *exc: Any) ->Any:
ifexc_typeisnotasyncio.CancelledError:
# propagate non-cancellation exceptionsreturnNoneifself._task.cancelling() >self._cancel_count:
# Task has been cancelled by something else - propagate itreturnNoneawaitself._stop.wait()
returnTruedef_cancel_task(self) ->None:
assertself._taskisnotNoneself._task.cancel()
self._cancel_count+=1 |
Archmonger
commented
Jul 24, 2023
Is there any way for us to use Python 3.11 asyncio features in older versions? I'm pretty sure the answer is no, and if so what do we want to do in the interim while we wait for 3.11 to become our minimum version? |
rmorshea
commented
Jul 24, 2023
I'll have to play around with whether this can be achieved with |
If we're going to introduce async specific Such as |
00c5830 to
72d4d66Compare
Limiting the default python version to 3.11 seems to have fixed it. |
rmorshea
commented
Nov 28, 2023
I'm going to close this and break up the changes into two parts:
|
By submitting this pull request you agree that all contributions to this project are made under the MIT license.
Issues
Closes: #956
Solution
Async effects now accept a "stop"
Eventthat is set when an effect needs to be re-run or a component is unmounting. The next effect will only run when the last effect has exited.Implementing this same behavior using sync effects is quite challenging:
To achieve this without requiring an implementation similar to the above, we've asyncified the internals of
Layout. This has the side-effect of allowing other things to happen while theLayoutis rendering (e.g. receive events, or allowing the server to respond to other requests). This potentially comes at the cost of rendering speed. For example, if a user is spamming the server with events renders may be interrupted in order to respond to them.Checklist
changelog.rsthas been updated with any significant changes.