Skip to content

Commit 9daa361

Browse files
author
Andrew J Westlake
committed
Made some README changes after proofread on GitHub
1 parent ca89b5c commit 9daa361

File tree

1 file changed

+19
-12
lines changed

1 file changed

+19
-12
lines changed

README.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -363,23 +363,25 @@ async fn main() -> PyResult<()> {
363363
364364
### Event Loop References and Thread-awareness
365365

366-
One problem that arises when interacting with Python's asyncio library is that the functions we use to get a reference to the Python event loop can only be called in certain contexts. Since PyO3 Asyncio requires references to the event loop when performing conversions between Rust and Python, this is unfortunately something that you need to worry about.
366+
One problem that arises when interacting with Python's asyncio library is that the functions we use to get a reference to the Python event loop can only be called in certain contexts. Since PyO3 Asyncio needs to interact with Python's event loop during conversions, the context of these conversions can matter a lot.
367+
368+
> The core conversions we've mentioned so far in this guide should insulate you from these concerns in most cases, but in the event that they don't, this section should provide you with the information you need to solve these problems.
367369
368370
#### The Main Dilemma
369371

370-
Python programs can have many independent event loop instances throughout the lifetime of the application (`asyncio.run` for example creates its own event loop each time it's called for instance), and they can even run concurrent with other event loops. The most correct method of obtaining a reference to the Python event loop is via `asyncio.get_running_loop`.
372+
Python programs can have many independent event loop instances throughout the lifetime of the application (`asyncio.run` for example creates its own event loop each time it's called for instance), and they can even run concurrent with other event loops. For this reason, the most correct method of obtaining a reference to the Python event loop is via `asyncio.get_running_loop`.
371373

372-
`asyncio.get_running_loop` returns the event loop associated with the current OS thread. It can be used inside Python coroutines to spawn concurrent tasks, interact with timers, or in our case signal between Rust and Python. This is all well and good when we are operating on a Python thread, but what happens when we want to perform a PyO3 Asyncio conversion on a _Rust_ thread? Since the Rust thread is not associated with a Python event loop, `asyncio.get_running_loop` will fail.
374+
`asyncio.get_running_loop` returns the event loop associated with the current OS thread. It can be used inside Python coroutines to spawn concurrent tasks, interact with timers, or in our case signal between Rust and Python. This is all well and good when we are operating on a Python thread, but since Rust threads are not associated with a Python event loop, `asyncio.get_running_loop` will fail when called on a Rust runtime.
373375

374376
#### The Solution
375377

376-
A really straightforward way of dealing with this problem is to pass a reference to the associated Python event loop for every conversion. That's why in PyO3 Asyncio, we introduced a new set of conversion functions that do just that:
378+
A really straightforward way of dealing with this problem is to pass a reference to the associated Python event loop for every conversion. That's why in `v0.14`, we introduced a new set of conversion functions that do just that:
377379

378380
- `pyo3_asyncio::into_future_with_loop` - Convert a Python awaitable into a Rust future with the given asyncio event loop.
379381
- `pyo3_asyncio::<runtime>::future_into_py_with_loop` - Convert a Rust future into a Python awaitable with the given asyncio event loop.
380382
- `pyo3_asyncio::<runtime>::local_future_into_py_with_loop` - Convert a `!Send` Rust future into a Python awaitable with the given asyncio event loop.
381383

382-
One clear disadvantage to this approach (besides the verbose naming) is that the Rust application has to explicitly track its references to the Python event loop. In native libraries, we can't make any assumptions about the underlying event loop, so the only reliable way to make sure our conversions work properly is to store a reference to the current event loop to use later on.
384+
One clear disadvantage to this approach (aside from the verbose naming) is that the Rust application has to explicitly track its references to the Python event loop. In native libraries, we can't make any assumptions about the underlying event loop, so the only reliable way to make sure our conversions work properly is to store a reference to the current event loop at the callsite to use later on.
383385

384386
```rust
385387
use pyo3::prelude::*;
@@ -420,7 +422,7 @@ Another disadvantage to this explicit approach that is less obvious is that we c
420422

421423
In order to detect the Python event loop at the callsite, we need something like `asyncio.get_running_loop` that works for _both Python and Rust_. In Python, `asyncio.get_running_loop` uses thread-local data to retrieve the event loop associated with the current thread. What we need in Rust is something that can retrieve the Python event loop associated with the current _task_.
422424

423-
Enter `pyo3_asyncio::<runtime>::get_current_loop`. This function first checks task-local data for a Python event loop, then falls back on `asyncio.get_running_loop` if no task-local event loop is found. This way both bases are convered.
425+
Enter `pyo3_asyncio::<runtime>::get_current_loop`. This function first checks task-local data for a Python event loop, then falls back on `asyncio.get_running_loop` if no task-local event loop is found. This way both bases are covered.
424426

425427
Now, all we need is a way to store the event loop in task-local data. Since this is a runtime-specific feature, you can find the following functions in each runtime module:
426428

@@ -440,6 +442,7 @@ fn sleep(py: Python) -> PyResult<&PyAny> {
440442

441443
pyo3_asyncio::tokio::future_into_py_with_loop(
442444
current_loop,
445+
// Store the current loop in task-local data
443446
pyo3_asyncio::tokio::scope(current_loop.into(), async move {
444447
let py_sleep = Python::with_gil(|py| {
445448
pyo3_asyncio::into_future_with_loop(
@@ -464,6 +467,7 @@ fn wrap_sleep(py: Python) -> PyResult<&PyAny> {
464467

465468
pyo3_asyncio::tokio::future_into_py_with_loop(
466469
current_loop,
470+
// Store the current loop in task-local data
467471
pyo3_asyncio::tokio::scope(current_loop.into(), async move {
468472
let py_sleep = Python::with_gil(|py| {
469473
pyo3_asyncio::into_future_with_loop(
@@ -491,11 +495,14 @@ fn my_mod(py: Python, m: &PyModule) -> PyResult<()> {
491495

492496
Even though this is more correct, it's clearly not more ergonomic. That's why we introduced a new set of functions with this functionality baked in:
493497

494-
- `pyo3_asyncio::<runtime>::into_future` - Convert a Python awaitable into a Rust future (using `pyo3_asyncio::<runtime>::get_current_loop`)
495-
- `pyo3_asyncio::<runtime>::future_into_py` - Convert a Rust future into a Python awaitable (using `pyo3_asyncio::<runtime>::get_current_loop` and `pyo3_asyncio::<runtime>::scope` to set the task-local event loop for the given Rust future)
496-
- `pyo3_asyncio::<runtime>::local_future_into_py` - Convert a `!Send` Rust future into a Python awaitable (using `pyo3_asyncio::<runtime>::get_current_loop` and `pyo3_asyncio::<runtime>::scope_local` to set the task-local event loop for the given Rust future).
498+
- `pyo3_asyncio::<runtime>::into_future`
499+
> Convert a Python awaitable into a Rust future (using `pyo3_asyncio::<runtime>::get_current_loop`)
500+
- `pyo3_asyncio::<runtime>::future_into_py`
501+
> Convert a Rust future into a Python awaitable (using `pyo3_asyncio::<runtime>::get_current_loop` and `pyo3_asyncio::<runtime>::scope` to set the task-local event loop for the given Rust future)
502+
- `pyo3_asyncio::<runtime>::local_future_into_py`
503+
> Convert a `!Send` Rust future into a Python awaitable (using `pyo3_asyncio::<runtime>::get_current_loop` and `pyo3_asyncio::<runtime>::scope_local` to set the task-local event loop for the given Rust future).
497504
498-
__These are the functions that we recommend using__. With these functions, the previous example can be written like so:
505+
__These are the functions that we recommend using__. With these functions, the previous example can be rewritten to be more compact:
499506

500507
```rust
501508
use pyo3::prelude::*;
@@ -545,10 +552,10 @@ Part of the reason why it's taken so long to push out a `v0.14` release is becau
545552

546553
This new release should address most the core issues that users have reported in the `v0.13` release, so I think we can expect more stability going forward.
547554

548-
Also, a special thanks to @ShadowJonathan for helping with the design and review
555+
Also, a special thanks to [@ShadowJonathan](https://github.com/ShadowJonathan) for helping with the design and review
549556
of these changes!
550557

551-
- @awestlake87
558+
- [@awestlake87](https://github.com/awestlake87)
552559

553560
## PyO3 Asyncio in Cargo Tests
554561

0 commit comments

Comments
 (0)