Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Better usability for threads #810
I was ok with
Maybe we should rename it to
Well, that seems popular.
Related idea: the whole "portal" design seems a bit more awkward than I was anticipating. I was imagining that threads were a mostly-for-experts kind of thing, and "foreign" threads would be as common as trio-spawned threads, but I feel like maybe it's not turning out that way.
Alternative idea (inspired by curio and anyio): provide a standard way to re-enter trio from a trio thread. Basically stash the token in thread-local storage, and then use it.
One possible API:
trio.run_from_thread(async_fn, *args, *, token=None) trio.run_sync_from_thread(fn, *args, *, token=None) trio.sync_cm_from_thread(cm, *, token=None) trio.async_cm_from_thread(cm, *, token=None) trio.run_sync_in_thread(fn, *args, *, ...) trio.sync_cm_in_thread(cm, *, ...)
So the general idea is that
This pattern might be used for other projects too, like trio-asyncio –
Alternatively, we could group these into a namespace, like:
trio.from_thread.run(...) trio.from_thread.run_sync(...) trio.from_thread.sync_cm(...) trio.from_thread.async_cm(...) trio.in_thread.run_sync(...) trio.in_thread.sync_cm(...)
This would group related functions together for tab-completion, and avoid "polluting" the main trio namespace. OTOH it looks a bit weird, and flat is better than nested...
Another alternative, or complement: we could do
Another possible approach:
trio.run_sync_in_thread(...) trio.threadsafe.check_cancelled() trio.threadsafe.run_in_trio(...) trio.threadsafe.run_sync_in_trio(...) trio.threadsafe.async_cm(...)
The idea is that in a thread, you're only allowed to use
I guess a downside to
I think these are unambiguous and fairly terse:
# These are the ones that are safe to use from a thread trio.from_thread.run(...) trio.from_thread.run_sync(...) trio.from_thread.cm(...) trio.from_thread.async_cm(...) trio.from_thread.check_cancel() # These are what you use from Trio trio.to_thread.run_sync(...) trio.to_thread.cm(...)
Alternative bikeshed color no. 1:
trio.to_trio.run(...) trio.to_trio.run_sync(...) trio.to_trio.cm(...) trio.to_trio.async_cm(...) trio.to_trio.check_cancel() trio.to_thread.run_sync(...) trio.to_thread.cm(...)
Alternative bikeshed color no. 2:
trio.trioify.run(...) trio.trioify.run_sync(...) trio.trioify.cm(...) trio.trioify.async_cm(...) trio.trioify.check_cancel() trio.threadify.run_sync(...) trio.threadify.cm(...)
Alternative bikeshed no. 3:
trio.as_trio.run(...) trio.as_trio.run_sync(...) trio.as_trio.cm(...) trio.as_trio.async_cm(...) trio.as_trio.check_cancel() trio.as_thread.run_sync(...) trio.as_thread.cm(...)
Alternative bikeshed no. I lost count:
# These are the ones that are safe to use from a thread trio.from_thread.run(...) trio.from_thread.run_sync(...) trio.from_thread.cm(...) trio.from_thread.async_cm(...) trio.from_thread.check_cancel() # These are what you use from Trio trio.use_thread.run_sync(...) trio.use_thread.cm(...)
Hmm. I think I prefer the "from thread" style over the "to trio" style, because if you're starting in asyncio or something then you need a different way to get "to trio". These are specifically: to trio from a thread that's running synchronous, blocking code. Which "from thread" doesn't exactly say, but if you had
There's still the
The context manager terminology is a bit confusing:
Maybe it's better to explicitly use
For the unusual case where someone wants to get into Trio from a thread that wasn't started by Trio: one option would be to have an object that implements the same stuff as
with entry_handle.register_thread(): trio.from_thread.run(...)
Possibly this should automatically close the handle at the end?
I guess there's an argument for using a
I am interested in working on this so that #1085 can be unblocked. From what I'm gathering from this conversation, what we want to do is change how Trio calls into threads by deprecating
@epellis OK good question :-)
So there are a few different issues with
So that's the big idea. But, we don't have to solve all of these problems at once – in particular, it's probably simplest to start by implementing a basic version of
So I think the next step is to implement
import trio def thread_fn(): start = trio.from_thread.run_sync(trio.current_time) print("In Trio-land, the time is now:", start) trio.from_thread.run(trio.sleep, 1) end = trio.from_thread.run_sync(trio.current_time) print("And now it's:", end) async def main(): await trio.run_sync_in_thread(thread_fn) trio.run(main)
#1122 converted the old
Not done yet:
These are interesting/tricky because they need to use the same task/thread to call both
latest install 0.12.0, gives message...
however, in to_thread.py it says...
thus the correct code should be
(from the pynng example pair1_async.py