trio-util
trio-util copied to clipboard
variant of wait() that guarantees that predicate matches when it returns
Would there be interest in adding a variant of wait
that only returns if the predicate is still True
? I find that I sometimes need to write stuff like:
foo = trio_util.AsyncValue("start")
# ... start up some other task that can modify foo
while True:
await foo.wait_value("bar")
if foo.value != "bar":
continue
# ... now we now that "foo" is bar, at least until we do something that yields control
Maybe something like:
await foo.wait_value("bar", recheck=True)
# ... now we now that "foo" is bar, at least until we do something that yields control
Would there be interest in adding a variant of wait that only returns if the predicate is still True?
It isn't possible to implement, based on the way coroutines and the Trio scheduler work. Given await foo()
, there is an arbitrary amount of time and task executions between the async foo()
returning and the caller receiving control.
Note that the AsyncValue API always returns the value that triggered the predicate. Many use cases rely on this, so that they don't miss fast-moving values. E.g.:
val = await foo.wait_value(lambda x: x > 10)
handle_high_foo(val) # guaranteed to be > 10
... now we know that "foo" is bar, at least until we do something that yields control
I'd argue that this is an uphill battle, because in general code tends to need async calls for something, and then your invariant is violated.
If you can share more details on your use case, I may be able to suggest something.
Hi @belm0!
It isn't possible to implement, based on the way coroutines and the Trio scheduler work. Given
await foo()
, there is an arbitrary amount of time and task executions between the asyncfoo()
returning and the caller receiving control.
From my reading of https://trio.readthedocs.io/en/stable/reference-core.html#checkpoints, I understand that the Trio scheduler only gets to reschedule tasks during checkpoints. Checkpoints happen in async functions that are built into Trio, e.g., trio.sleep
, trio.lowlevel.checkpoint
. Internally these functions will call into Trio's scheduler to pause these tasks, and typically will have some kind of callback that will tell Trio to wake these tasks up. My understanding is that there isn't any magic in the awaiting on an arbitrary async function that would allow Trio to create a scheduling point.
So it's my understanding that in this:
async def foo():
print("a")
await trio.sleep(0)
print("b")
async def bar():
await foo()
print("c")
...
await bar()
we might get paused between "a" and "b", but Trio will never have the opportunity to interrupt us between printing "b" and "c".
Please let me know if I'm misunderstanding how async/Trio works, I'm relatively new to async & Trio. :)
That's correct. I see, so the wait function is checking the value, and either returning directly to the caller or going back to sleep.
Along the lines of my other comment about code tending to need async calls, I guess in practice I've learned not to rely on that property. For example, events are often layered or aggregated, say by passing several wait_value() calls to wait_any()
, which will introduce intermediate scheduling.
The use case is a little odd: we're saying that as long as time is frozen, you'd want to act on the event. But if you advance time by even 1 ms (i.e. let another task run), the value may be changed, and then you don't want to act on it. At least in our application (soft real-time robot) I don't think that's a common pattern-- but I'd like to keep an eye out for it over some time, and think about the use case.