
<p>本文旨在解决Python异步编程中,如何在不使用`aw
ait`的情况下启动协程并控制其执行流程的问题。通过引入`asyncio.run_coroutine_threadsafe`,我们可以在独立的事件循环中运行协程,从而实现类似JavaScript中`async`函数立即执行的效果。本文将提供详细的代码示例和解释,帮助开发者更好地理解和运用这种模式,解决异步编程中的实际挑战。</p>
在Python的`asyncio`库中,直接调用一个协程并不会立即执行它。协程只有在被`await`或者通过`asyncio.create_task`等方式调度后才会开始运行。这与JavaScript等语言中`async`函数的行为有所不同,后者会在调用时立即执行直到遇到第一个`await`。本文将探讨如何在Python中实现类似JavaScript的异步编程模式,即在不阻塞主线程的情况下启动协程,并在后续的某个时刻获取其结果。
### 使用`asyncio.run_coroutine_threadsafe`在独立线程中运行协程
一种解决方案是使用`asyncio.run_coroutine_threadsafe`函数。该函数允许我们在一个独立的事件循环中运行协程,而不会阻塞当前线程。这对于需要在后台执行
异步任务,并且不希望影响主线程的响应性的场景非常有用。
以下是一个示例代码,演示了如何使用`asyncio.run_coroutine_threadsafe`:
```
python
import asyncio
import time
from threading import Thread
global_loop = None
def thread_for_event_loop():
global global_loop
global_loop = asyncio.new_event_loop()
asyncio.set_event_loop(global_loop)
global_loop.run_forever()
t = Thread(target=thread_for_event_loop)
t.daemon = True
t.start()
time.sleep(1) # wait for thread to start
old_print = print
print = lambda *_: old_print(round(time.perf_counter(), 1), *_)
def attempt(future): # doesn't actually do anything, only prints if task is done
print(future.done())
async def work():
print("SETUP")
await asyncio.sleep(2)
print("MIDDLE")
await asyncio.sleep(2)
print("END")
return "Result"
async def main():
print("START", int(time.perf_counter()))
task = asyncio.run_coroutine_threadsafe(work(), global_loop)
attempt(task)
attempt(task)
print("before first sleep")
time.sleep(3)
print("after first sleep")
attempt(task)
attempt(task)
print("before second sleep")
time.sleep(3) # Block CPU to wait for second sleeping to finish
print("after second sleep")
attempt(task)
attempt(task)
print(await asyncio.wrap_future(task))
asyncio.run(main())
代码解释:
-
创建独立的事件循环和线程: 首先,我们创建一个新的事件循环,并在一个独立的线程中运行它。这是通过thread_for_event_loop函数实现的。asyncio.new_event_loop() 创建一个全新的事件循环,asyncio.set_event_loop(global_loop) 将其设置为当前线程的事件循环,然后 global_loop.run_forever() 启动事件循环,使其持续运行直到被显式停止。
-
asyncio.run_coroutine_threadsafe的使用: 在main协程中,我们使用asyncio.run_coroutine_threadsafe(work(), global_loop)将work协程提交到独立的事件循环中运行。这个函数返回一个concurrent.futures.Future对象,可以用来追踪协程的完成状态。
-
attempt 函数: attempt 函数用于检查 Future 对象是否已完成。它通过调用 future.done() 来实现,并打印结果。
-
asyncio.wrap_future的使用: asyncio.wrap_future(task) 用于将 concurrent.futures.Future 对象转换为 asyncio.Future 对象,以便可以在 await 表达式中使用它。这允许主协程等待在独立线程中运行的协程完成。
输出结果分析:
代码的输出展示了协程在独立线程中的执行流程:
1.1 START 1
1.1 False
1.1 False
1.1 before first sleep
1.1 SETUP
3.1 MIDDLE
4.1 after first sleep
4.1 False
4.1 False
4.1 before second sleep
5.1 END
7.1 after second sleep
7.1 True
7.1 True
7.1 Result
登录后复制
- "START" 和 "SETUP" 的打印时间非常接近,说明 work 协程在 main 协程启动后立即开始执行。
- attempt(task) 在 time.sleep 之前返回 False,表明 work 协程尚未完成。
- 在两个 time.sleep 之后,attempt(task) 仍然返回 False,表明 work 协程仍在后台运行。
- 最后,await asyncio.wrap_future(task) 返回了 work 协程的结果 "Result"。
注意事项和总结
-
线程安全: 在使用asyncio.run_coroutine_threadsafe时,需要注意线程安全问题。确保在协程中访问的共享资源是线程安全的。
-
错误处理: 如果协程在独立线程中引发异常,该异常不会自动传播到主线程。需要通过Future对象来捕获和处理异常。
-
事件循环管理: 需要小心管理独立的事件循环的生命周期。确保在不再需要时正确关闭事件循环。
-
替代方案: 也可以考虑使用asyncio.create_task创建任务,并使用asyncio.gather等函数来并发执行多个任务。但这种方式仍然需要在主线程中使用await来等待任务完成。
总而言之,asyncio.run_coroutine_threadsafe提供了一种在Python中实现类似JavaScript的异步编程模式的方法,即在不阻塞主线程的情况下启动协程。通过合理地使用这种模式,可以提高应用程序的响应性和并发性。
以上就是Python异步编程进阶:在不阻塞主线程的情况下启动和管理协程的详细内容,更多请关注php中文网其它相关文章!