-
-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better usability for threads #810
Comments
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 See also: #680 for context managers across threads, #606 for propagating cancellation across trio->thread->trio transitions, and #648 for preserving contextvars across thread switches |
The opposite of "from" is "to", so |
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 |
Another reason to reconsider the |
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 OTOH, 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 |
Maybe #1099 was a mistake though... if we're going to settle on |
(Note that it's cheap to change #1099 before the next release.) |
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
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) |
For consistency with trio.from_thread, and to give us a place for future extensions, like utilities for pushing context managers into threads. See python-triogh-810.
#1122 converted the old Not done yet:
These are interesting/tricky because they need to use the same task/thread to call both That means they might overlap with #606, which would also involve using the same task for multiple entries to trio (#606 (comment)) |
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 |
@MauiJerry Nice catch! That should be fixed by #1177. |
Speaking of using the same task when entering Trio repeatedly from the same thread: we should probably also do the reverse. If you go thread→trio→back to thread, we should re-use the original thread, instead of creating a new one. This is important for two reasons.
There's a bit of a subtlety to doing this optimally. The key property is that I think that means: If the Also, we have to be careful that if we go thread→trio→spawn two tasks→both tasks go back to a thread, then only one of them can re-use the pre-existing thread at a time. I think we can manage all this if our threading code maintains a global table of which threads are currently blocked in Phew. |
Not sure if this belongs here, but it fits the issue title. I would find it quite useful if there were an additional API:
Critically, note that there's no need for the Trio token - it would work from any thread and automatically figure out the Trio thread (or threads? but presumably it's an error to use the same cancellation scope in multiple threads at once). Or, if the Before I tell you my motivation, let me fully acknowledge that it's quite a small convenience, so if this requires a lot of machinary behind the scenes then it's surely not worth it. But I thought I should at least mention this use case, as it could be quite common (certainly it is for me). Imagine that you want to use Trio but have some application that has its own top-level code that you don't want to convert to Trio (shocking I know), so you spawn a thread, run Trio in it for a bit (passing back data e.g. with a
That's a fair amount of boilerplate, whereas starting Trio in a thread to do a bit of work is otherwise suprisingly little code. It's also a bit racy (what if the variables are still But actually it's quite close to being a lot shorter: I could've instantiated the
If you could replace the last line with |
I just discovered that my first snippet is racy in two ways. I already identified that we might try to stop the Trio thread before it has started (so |
I was ok with
run_sync_in_worker_thread
having a slightly-awkward name because threads are going to make your life awkward so, you know, it's fair warning. But when you're stuck using threads to hack around missing trio libraries, it can feel like a bit of extra punishment on top, which is not so nice.Maybe we should rename it to
run_sync_in_thread
?The text was updated successfully, but these errors were encountered: