0

0

Python异步任务与同步代码集成:背景执行与完成策略

碧海醫心

碧海醫心

发布时间:2025-11-30 11:05:40

|

215人浏览过

|

来源于php中文网

原创

python异步任务与同步代码集成:背景执行与完成策略

本文深入探讨了在Python同步应用中集成异步协程作为后台任务的挑战与解决方案。通过分析`asyncio.create_task`在`asyncio.run`环境下的行为,我们揭示了异步并发与线程并行之间的差异。文章提供了两种核心策略:通过在事件循环内显式`await`任务以确保其顺序完成,以及利用`threading`将`asyncio`事件循环隔离到单独线程以实现真正的并行执行,从而有效管理异步任务的生命周期。

理解asyncio的并发机制

在Python中,asyncio提供了一种基于协程的并发编程模型,它通过事件循环(event loop)实现协作式多任务。与传统的多线程并行不同,asyncio在单个线程内通过在await表达式处挂起当前协程并切换到其他就绪协程来模拟并发执行。这意味着,如果一个协程内部没有await语句或者遇到一个同步阻塞操作(如time.sleep()),它将独占CPU直到完成,从而阻塞整个事件循环,阻止其他协程的执行。

当我们在一个同步函数中尝试通过asyncio.run()启动一个包含asyncio.create_task()的异步函数时,一个常见的误解是认为create_task()会自动在后台“并行”运行并完成。然而,create_task()仅仅是调度了一个协程,将其放入事件循环的待执行队列中。如果这个被调度的任务没有被显式地await,或者事件循环在任务完成之前就结束了,那么该任务可能只执行到第一个await点就被挂起,并且永远不会恢复执行。

考虑以下示例代码,它展示了asyncio.create_task()在未被await时可能遇到的问题:

立即学习Python免费学习笔记(深入)”;

import asyncio
from time import sleep
import sys

async def task():
    """一个模拟后台工作的异步协程"""
    for i in range(5):
        print(f"Background task iteration {i}")
        await asyncio.sleep(1) # 模拟异步IO操作
    print('finished')

async def background_task_wrapper():
    """包装器,用于调度后台任务"""
    print("a")
    asyncio.create_task(task()) # 仅仅调度,未await
    print("b")

def main():
    """主同步程序"""
    print("Main program started python", sys.version)
    asyncio.run(background_task_wrapper()) # 运行异步部分

    # 异步部分结束后,主程序继续同步执行
    for i in range(3):
        sleep(3) # 同步阻塞
        print(f"Main program iteration {i}")

if __name__ == "__main__":
    main()

运行上述代码,你会发现task()协程只打印了"Background task iteration 0"就停止了,后续的迭代和"finished"消息都没有出现。这是因为asyncio.run(background_task_wrapper())执行完毕后,事件循环就关闭了。虽然task()被调度了,但它在第一次await asyncio.sleep(1)时挂起,而主程序的sleep(3)是同步阻塞的,与asyncio事件循环无关,因此事件循环没有机会恢复task()的执行。

解决方案一:在事件循环内确保任务完成

如果你的“后台任务”需要在asyncio.run()调用的生命周期内完成,即使它不直接影响后续代码的执行流程,也应该在事件循环内部显式地await它。这确保了事件循环会等待该任务执行完毕才退出。

以下是修正后的代码示例:

Video Summarization
Video Summarization

一款可以自动将长视频制作成短片的桌面软件

下载
import asyncio
from time import sleep
import sys

async def task():
    """一个模拟后台工作的异步协程"""
    for i in range(5):
        print(f"Background task iteration {i}")
        await asyncio.sleep(0.1) # 缩短睡眠时间以便观察
    print('finished')

async def background_task_wrapper_await():
    """包装器,显式await后台任务"""
    print("a")
    scheduled_task = asyncio.create_task(task()) # 调度任务
    print("task scheduled")
    # 可以在这里执行其他不阻塞的异步操作
    await scheduled_task # 显式等待任务完成
    print("b")

def main_solution_one():
    """主同步程序 - 方案一"""
    print("Main program started python", sys.version)
    asyncio.run(background_task_wrapper_await()) # 运行异步部分

    # 异步部分结束后,主程序继续同步执行
    for i in range(3):
        sleep(0.5) # 缩短睡眠时间以便观察
        print(f"Main program iteration {i}")

if __name__ == "__main__":
    main_solution_one()

输出示例:

Main program started python 3.x.x (...)
a
task scheduled
Background task iteration 0
Background task iteration 1
Background task iteration 2
Background task iteration 3
Background task iteration 4
finished
b
Main program iteration 0
Main program iteration 1
Main program iteration 2

通过在background_task_wrapper_await函数中添加await scheduled_task,我们确保了task()协程在asyncio.run()返回之前能够完整执行。这种方法适用于异步任务是整个asyncio事件循环的一部分,并且其完成是同步代码继续执行的先决条件(或至少在asyncio部分完成之前)。

解决方案二:利用线程实现真正的并行执行

如果你的需求是让异步任务在后台真正地与主同步程序并行运行,即主程序的同步阻塞操作(如time.sleep())不应该影响异步任务的执行,那么你需要将asyncio事件循环本身放到一个单独的线程中。这样,主线程可以执行其同步代码,而另一个线程则专门运行asyncio事件循环。

以下是结合threading库实现并行执行的示例:

import asyncio
from time import sleep
import sys
import threading

async def task():
    """一个模拟后台工作的异步协程"""
    for i in range(5):
        print(f"Background task iteration {i}")
        await asyncio.sleep(1) # 模拟异步IO操作
    print('finished')

async def background_task_thread_entry():
    """作为线程入口的异步函数,直接await任务"""
    print("a")
    await task() # 在此上下文,可以直接await,无需create_task再await
    print("b")

def main_solution_two():
    """主同步程序 - 方案二,结合线程"""
    print("Main program started python", sys.version)

    # 在一个新线程中运行asyncio事件循环
    # target是线程要执行的函数,args是传递给该函数的参数
    t = threading.Thread(target=lambda: asyncio.run(background_task_thread_entry()))
    t.start() # 启动线程

    # 主程序继续同步执行
    for i in range(3):
        sleep(3) # 同步阻塞
        print(f"Main program iteration {i}")

    # 可选:等待后台线程完成,确保所有输出都显示
    # t.join() 
    # 注意:如果主程序先于后台任务完成,t.join()会阻塞主程序直到后台任务完成。
    # 根据具体需求决定是否需要调用。

if __name__ == "__main__":
    main_solution_two()

输出示例:

Main program started python 3.x.x (...)
a
Background task iteration 0
Background task iteration 1
Background task iteration 2
Main program iteration 0
Background task iteration 3
Background task iteration 4
finished
b
Main program iteration 1
Main program iteration 2

在这个方案中,asyncio.run(background_task_thread_entry())被封装在一个threading.Thread对象中,并在单独的线程中启动。这样,主线程的sleep(3)不再阻塞asyncio事件循环,两个部分可以独立并行地执行。你会看到主程序的迭代和后台任务的迭代是交错进行的。

关键考虑与最佳实践

  1. 选择合适的方案:
    • 如果异步任务的完成是当前asyncio.run()块的逻辑组成部分,且不期望与外部同步代码并行,使用方案一(内部await)
    • 如果异步任务需要与主同步代码完全并行执行,互不干扰,使用方案二(线程化asyncio)
  2. 避免在asyncio协程中进行同步阻塞操作: 无论采用哪种方案,asyncio协程内部都应避免直接调用time.sleep()、同步I/O操作(如文件读写、网络请求)或长时间的CPU密集型计算。这些操作会阻塞事件循环,导致所有其他协程停滞。应使用await asyncio.sleep()、aiofiles、aiohttp等异步库,或将CPU密集型任务 offload 到ThreadPoolExecutor或ProcessPoolExecutor。
  3. 线程管理与通信: 如果使用线程方案,需要考虑主线程与asyncio线程之间的通信机制(例如,使用queue.Queue、asyncio.Queue结合线程安全锁,或asyncio.run_coroutine_threadsafe)。
  4. 异常处理: 在asyncio.create_task()后,如果任务可能失败,应该考虑如何处理异常。await一个任务时,如果任务抛出异常,await表达式会重新抛出该异常。如果只是create_task而不await,异常可能会被忽略,除非你添加了异常回调。
  5. 资源清理: 确保在程序结束时,所有启动的线程和异步任务都能正确关闭和清理资源。对于线程,通常需要join()来等待其完成。

总结

在Python同步应用程序中运行异步协程作为后台任务时,理解asyncio的协作式多任务特性至关重要。简单地使用asyncio.create_task()而不进行后续处理,可能导致任务未完成就被事件循环关闭。通过在事件循环内部显式await任务,可以确保其在asyncio.run()的生命周期内完成。而若要实现真正的与主同步代码并行执行,则需要将整个asyncio事件循环封装在一个单独的线程中运行。根据具体的应用场景和对并行性、任务完成时机的要求,选择合适的集成策略是构建健壮混合应用的基石。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

502

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

7

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

14

2026.01.21

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

7

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

14

2026.01.21

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

87

2025.12.01

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Django 教程
Django 教程

共28课时 | 3.6万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号