23. Python Asyncio 常见错误¶
23. Python Asyncio Common Errors
本节提供了开发人员在 Python 中使用 asyncio 时遇到的常见错误的示例。
5 个最常见的异步错误是:
- 尝试通过调用协程来运行它们。
- 不让协程在事件循环中运行。
- 使用 asyncio 低级 API。
- 过早退出主协程。
- 假设竞争条件和死锁是不存在的。
让我们依次仔细看看每一个问题。
This section gives examples of general errors encountered by developers when using asyncio in Python.
The 5 most common asyncio errors are:
- Trying to run coroutines by calling them.
- Not letting coroutines run in the event loop.
- Using the asyncio low-level API.
- Exiting the main coroutine too early.
- Assuming race conditions and deadlocks are not possible.
Let’s take a closer look at each in turn.
23.1 错误 1: 尝试通过函数调用的方式来运行协程¶
23.1 Error 1: Trying to Run Coroutines by Calling Them
asyncio 初学者遇到的最常见错误是像函数一样调用协程。
例如,我们可以使用“async def”表达式定义一个协程:
# 自定义协程
async def custom_coro():
print('hi there')
然后,初学者将尝试像函数一样调用这个协程,并期望报告打印消息。
例如:
...
# 尝试像函数一样调用协程时出错
custom_coro()
像函数一样调用协程不会执行协程主体。
相反,它将创建一个协程对象。
然后可以在 asyncio 运行时中等待该对象,例如:事件循环(Event Loop)。
我们可以使用 asyncio.run()
函数启动事件循环来运行协程。
例如:
...
# 运行协程
asyncio.run(custom_coro())
或者,我们可以挂起当前协程并使用“await”表达式调度另一个协程。
例如:
...
# 调度一个协程
await custom_coro()
您可以在教程中了解有关运行协程的更多信息:
The most common error encountered by beginners to asyncio is calling a coroutine like a function.
For example, we can define a coroutine using the “async def” expression:
# custom coroutine
async def custom_coro():
print('hi there')
The beginner will then attempt to call this coroutine like a function and expect the print message to be reported.
For example:
...
# error attempt at calling a coroutine like a function
custom_coro()
Calling a coroutine like a function will not execute the body of the coroutine.
Instead, it will create a coroutine object.
This object can then be awaited within the asyncio runtime, e.g. the event loop.
We can start the event loop to run the coroutine using the asyncio.run() function.
For example:
...
# run a coroutine
asyncio.run(custom_coro())
Alternatively, we can suspend the current coroutine and schedule the other coroutine using the “await” expression.
For example:
...
# schedule a coroutine
await custom_coro()
You can learn more about running coroutines in the tutorial:
23.2 错误 2: 不在事件循环中运行协程¶
23.2 Error 2: Not Letting Coroutines Run in the Event Loop
如果协程未运行,您将收到如下运行时警告:
sys:1: RuntimeWarning: coroutine 'custom_coro' was never awaited
如果您创建一个协程对象但没有安排它在 asyncio 事件循环中执行,就会发生这种情况。
例如,您可以尝试从常规 Python 程序调用协程:
...
# 尝试调用协程
custom_coro()
这不会调用协程。
相反,它将创建一个协程对象。
例如:
...
# 创建一个协程对象
coro = custom_coro()
如果您不允许该协程运行,您将收到运行时错误。
正如我们在上一节中看到的,您可以通过启动 asyncio 事件循环并向其传递协程对象来让协程运行。
例如:
...
# 创建一个协程对象
coro = custom_coro()
# 运行协程
asyncio.run(coro)
或者,在复合语句的一行中:
...
# 运行协程
asyncio.run(custom_coro())
您可以在教程中了解有关运行协程的更多信息:
如果您在 asyncio 程序中收到此错误,那是因为您创建了一个协程但尚未安排其执行。
这可以使用 await 表达式来实现。
例如:
...
# 创建一个协程对象
coro = custom_coro()
# 挂起并允许其他协程运行
await coro
或者,您可以安排它作为任务独立运行。
例如:
...
# 创建一个协程对象
coro = custom_coro()
# 安排 coro 作为任务相互依赖地运行
task = asyncio.create_task(coro)
您可以在教程中了解有关创建任务的更多信息:
If a coroutine is not run, you will get a runtime warning as follows:
sys:1: RuntimeWarning: coroutine 'custom_coro' was never awaited
This will happen if you create a coroutine object but do not schedule it for execution within the asyncio event loop.
For example, you may attempt to call a coroutine from a regular Python program:
...
# attempt to call the coroutine
custom_coro()
This will not call the coroutine.
Instead, it will create a coroutine object.
For example:
...
# create a coroutine object
coro = custom_coro()
If you do not allow this coroutine to run, you will get a runtime error.
You can let the coroutine run, as we saw in the previous section, by starting the asyncio event loop and passing it the coroutine object.
For example:
...
# create a coroutine object
coro = custom_coro()
# run a coroutine
asyncio.run(coro)
Or, on one line in a compound statement:
...
# run a coroutine
asyncio.run(custom_coro())
You can learn more about running coroutines in the tutorial:
If you get this error within an asyncio program, it is because you have created a coroutine and have not scheduled it for execution.
This can be achieved using the await expression.
For example:
...
# create a coroutine object
coro = custom_coro()
# suspend and allow the other coroutine to run
await coro
Or, you can schedule it to run independently as a task.
For example:
...
# create a coroutine object
coro = custom_coro()
# schedule the coro to run as a task interdependently
task = asyncio.create_task(coro)
You can learn more about creating tasks in the tutorial:
- How to Create an Asyncio Task in Python
23.3 错误 3: 使用低级的 Asyncio API¶
23.3 Error 3: Using the Low-Level Asyncio API
初学者的一个大问题是他们使用了错误的 asyncio API。
由于多种原因,这种情况很常见。
- API 在最新版本的 Python 中发生了很大变化。
- API 文档页面显示了这两个 API,这让事情变得混乱。
- 网络上其他地方的示例混合使用不同的 API。
使用错误的 API 会使事情变得更加冗长(例如更多代码)、更加困难并且更难以理解。
Asyncio 提供两个 API.
- 面向应用程序开发人员(我们)的高级 API
- 面向框架和库开发人员(不是我们)的低级 API
较低级别的 API 为高级 API 提供基础,包括事件循环、传输协议、策略等的内部结构。
… 有供库和框架开发人员使用的低级 API
我们几乎应该始终坚持使用高级 API。
开始时我们绝对必须坚持使用高级 API。
有时我们可能会利用低级 API 来实现特定的结果。
如果您开始获取事件循环的句柄或使用“loop”变量来执行操作,那么您就做错了。
我并不是说不要学习低级 API。
大胆试试吧。 这很棒。
只是不要从那里开始。
通过高级 API 驱动 asyncio 一段时间。 开发一些程序。 熟悉异步编程并随意运行协程。
然后,深入看看相关技术细节。
A big problem with beginners is that they use the wrong asyncio API.
This is common for a number of reasons.
- The API has changed a lot with recent versions of Python.
- The API docs page makes things confusing, showing both APIs.
- Examples elsewhere on the web mix up using the different APIs.
Using the wrong API makes things more verbose (e.g. more code), more difficult, and way less understandable.
Asyncio offers two APIs.
- High-level API for application developers (us)
- Low-level API for framework and library developers (not us)
The lower-level API provides the foundation for the high-level API and includes the internals of the event loop, transport protocols, policies, and more.
… there are low-level APIs for library and framework developers
— ASYNCIO — ASYNCHRONOUS I/O We should almost always stick to the high-level API.
We absolutely must stick to the high-level API when getting started.
We may dip into the low-level API to achieve specific outcomes on occasion.
If you start getting a handle on the event loop or use a “loop” variable to do things, you are doing it wrong.
I am not saying don’t learn the low-level API.
Go for it. It’s great.
Just don’t start there.
Drive asyncio via the high-level API for a while. Develop some programs. Get comfortable with asynchronous programming and running coroutines at will.
Then later, dip in and have a look around.
23.4 错误 4: 退出主协程太早¶
23.4 Error 4: Exiting the Main Coroutine Too Early
异步程序中的一个主要混乱点是没有给任务足够的时间来完成。
我们可以通过 asyncio.create_task() 方法安排许多协程在 asyncio 程序中独立运行。
主协程(asyncio 程序的入口点)可以继续执行其他活动。
如果主协程退出,则 asyncio 程序将终止。
即使有一个或多个协程作为任务独立运行,程序也会终止。
这可能会让你措手不及。
您可以发出许多任务,然后允许主协程恢复,并期望所有发出的任务都能在自己的时间内完成。
相反,如果主协程没有其他事情可做,它应该等待剩余的任务。
这可以通过首先通过 asyncio.all_tasks() 函数获取一组所有正在运行的任务,将其自身从该组中删除,然后通过 asyncio.wait() 函数等待剩余任务来实现。
例如:
...
# 获取所有正在运行的任务的集合
all_tasks = asyncio.all_tasks()
# 获取当前任务
current_task = asyncio.current_task()
# 从所有任务列表中删除当前任务
all_tasks.remove(current_task)
# 暂停直到所有任务完成
await asyncio.wait(all_tasks)
A major point of confusion in asyncio programs is not giving tasks enough time to complete.
We can schedule many coroutines to run independently within an asyncio program via the asyncio.create_task() method.
The main coroutine, the entry point for the asyncio program, can then carry on with other activities.
If the main coroutine exits, then the asyncio program will terminate.
The program will terminate even if there are one or many coroutines running independently as tasks.
This can catch you off guard.
You may issue many tasks and then allow the main coroutine to resume, expecting all issued tasks to complete in their own time.
Instead, if the main coroutine has nothing else to do, it should wait on the remaining tasks.
This can be achieved by first getting a set of all running tasks via the asyncio.all_tasks() function, removing itself from this set, then waiting on the remaining tasks via the asyncio.wait() function.
For example:
...
# get a set of all running tasks
all_tasks = asyncio.all_tasks()
# get the current tasks
current_task = asyncio.current_task()
# remove the current task from the list of all tasks
all_tasks.remove(current_task)
# suspend until all tasks are completed
await asyncio.wait(all_tasks)
23.5 错误 5: 假设竞争条件和死锁是不可能的¶
23.5 Error 5: Assuming Race Conditions and Deadlocks are Impossible
并发编程存在并发特定故障模式的危险。
这包括竞争条件和死锁等问题。
竞争条件涉及两个或多个并发单元同时执行同一关键部分,并使资源或数据处于不一致或意外状态。 这可能会导致数据损坏和数据丢失。
死锁是指并发单元等待永远不会发生的条件,例如资源可用。
许多 Python 开发人员认为 asyncio 中的协程不可能出现这些问题。
原因是任一时间只有一个协程可以在事件循环内运行。
确实,一次只能运行一个协程。
问题是,协程可以挂起和恢复,并且可以在使用共享资源或共享变量时执行此操作。
如果不保护关键部分,异步程序中可能会出现竞争条件。
如果不仔细管理同步原语,可能会发生死锁
因此,创建 asyncio 程序以确保协程安全(类似于线程安全和进程安全的概念)非常重要,适用于协程。
Concurrent programming has the hazard of concurrency-specific failure modes.
This includes problems such as race conditions and deadlocks.
A race condition involves two or more units of concurrency executing the same critical section at the same time and leaving a resource or data in an inconsistent or unexpected state. This can lead to data corruption and data loss.
A deadlock is when a unit of concurrency waits for a condition that can never occur, such as for a resource to become available.
Many Python developers believe these problems are not possible with coroutines in asyncio.
The reason being that only one coroutine can run within the event loop at any one time.
It is true that only one coroutine can run at a time.
The problem is, coroutines can suspend and resume and may do so while using a shared resource or shared variable.
Without protecting critical sections, race conditions can occur in asyncio programs.
Without careful management of synchronization primitives, deadlocks can occur
As such, it is important that asyncio programs are created ensuring coroutine-safety, a concept similar to thread-safety and process-safety, applied to coroutines.
创建日期: 2024年9月4日