Python异步编程详解

Python异步编程详解

引言

异步编程是Python中处理并发操作的重要方式,它允许程序在等待I/O操作时执行其他任务,从而提高程序的整体效率。本文将详细介绍Python异步编程的概念、实现方式以及实际应用场景。

1. 异步编程基础

1.1 什么是异步编程?

异步编程是一种非阻塞的编程模式,它允许程序在等待某个操作完成时继续执行其他任务。与传统的同步编程相比,异步编程更适合处理I/O密集型任务。

python 复制代码
# 同步方式
def sync_function():
    result1 = long_running_operation1()  # 阻塞
    result2 = long_running_operation2()  # 阻塞
    return result1 + result2

# 异步方式
async def async_function():
    result1 = await long_running_operation1()  # 非阻塞
    result2 = await long_running_operation2()  # 非阻塞
    return result1 + result2

1.2 异步编程的优势

  • 提高I/O密集型应用的性能
  • 更好的资源利用率
  • 支持高并发操作
  • 避免线程切换开销

2. 异步编程的实现

2.1 使用asyncio

python 复制代码
import asyncio

async def main():
    # 创建任务
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    
    # 等待任务完成
    await task1
    await task2

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

# 运行主函数
asyncio.run(main())

2.2 异步函数定义

python 复制代码
import asyncio

async def fetch_data(url):
    # 模拟网络请求
    await asyncio.sleep(1)
    return f"Data from {url}"

async def process_data(data):
    # 模拟数据处理
    await asyncio.sleep(0.5)
    return f"Processed: {data}"

async def main():
    # 并发执行多个任务
    urls = ['url1', 'url2', 'url3']
    tasks = [fetch_data(url) for url in urls]
    results = await asyncio.gather(*tasks)
    
    # 处理结果
    processed_results = await asyncio.gather(
        *[process_data(result) for result in results]
    )
    print(processed_results)

asyncio.run(main())

3. 实际应用场景

3.1 网络请求

python 复制代码
import aiohttp
import asyncio

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        urls = [
            'http://example.com/1',
            'http://example.com/2',
            'http://example.com/3'
        ]
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# 运行示例
results = asyncio.run(main())

3.2 文件操作

python 复制代码
import asyncio
import aiofiles

async def read_file(filename):
    async with aiofiles.open(filename, mode='r') as f:
        return await f.read()

async def write_file(filename, content):
    async with aiofiles.open(filename, mode='w') as f:
        await f.write(content)

async def main():
    # 并发读写文件
    content = await read_file('input.txt')
    await write_file('output.txt', content.upper())

asyncio.run(main())

4. 高级特性

4.1 超时控制

python 复制代码
import asyncio

async def long_running_task():
    await asyncio.sleep(10)
    return "Task completed"

async def main():
    try:
        # 设置超时时间
        result = await asyncio.wait_for(long_running_task(), timeout=5.0)
        print(result)
    except asyncio.TimeoutError:
        print("Task timed out")

asyncio.run(main())

4.2 并发限制

python 复制代码
import asyncio
import aiohttp

async def fetch_with_semaphore(sem, session, url):
    async with sem:
        async with session.get(url) as response:
            return await response.text()

async def main():
    # 限制并发数为3
    sem = asyncio.Semaphore(3)
    async with aiohttp.ClientSession() as session:
        tasks = [
            fetch_with_semaphore(sem, session, url)
            for url in ['http://example.com'] * 10
        ]
        results = await asyncio.gather(*tasks)
        return results

asyncio.run(main())

5. 最佳实践

5.1 错误处理

python 复制代码
import asyncio

async def risky_operation():
    if random.random() < 0.5:
        raise ValueError("Random error")
    return "Success"

async def main():
    try:
        result = await risky_operation()
        print(result)
    except ValueError as e:
        print(f"Error occurred: {e}")
    except Exception as e:
        print(f"Unexpected error: {e}")

asyncio.run(main())

5.2 资源管理

python 复制代码
import asyncio
from contextlib import asynccontextmanager

@asynccontextmanager
async def managed_resource():
    print("Acquiring resource")
    try:
        yield "resource"
    finally:
        print("Releasing resource")

async def main():
    async with managed_resource() as resource:
        print(f"Using {resource}")
        await asyncio.sleep(1)

asyncio.run(main())

6. 实际应用示例

6.1 异步Web服务器

python 复制代码
from aiohttp import web
import asyncio

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = f"Hello, {name}"
    return web.Response(text=text)

async def main():
    app = web.Application()
    app.router.add_get('/', handle)
    app.router.add_get('/{name}', handle)
    
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, 'localhost', 8080)
    await site.start()
    
    print("Server started at http://localhost:8080")
    while True:
        await asyncio.sleep(3600)  # 运行1小时

asyncio.run(main())

6.2 异步数据库操作

python 复制代码
import asyncio
import asyncpg

async def main():
    # 创建连接池
    pool = await asyncpg.create_pool(
        user='postgres',
        password='password',
        database='mydb',
        host='localhost'
    )
    
    async with pool.acquire() as conn:
        # 执行查询
        rows = await conn.fetch('SELECT * FROM users')
        for row in rows:
            print(row)
    
    await pool.close()

asyncio.run(main())

练习

1. 实现一个异步的聊天服务器

python 复制代码
import asyncio
import json
from datetime import datetime

class ChatServer:
    def __init__(self, host='localhost', port=8888):
        self.host = host
        self.port = port
        self.clients = {}  # 存储客户端连接
        self.messages = []  # 存储聊天记录

    async def handle_client(self, reader, writer):
        # 获取客户端地址
        addr = writer.get_extra_info('peername')
        print(f"New connection from {addr}")
        
        # 生成客户端ID
        client_id = f"client_{len(self.clients)}"
        self.clients[client_id] = writer
        
        try:
            # 发送欢迎消息
            welcome_msg = {
                'type': 'system',
                'content': f'Welcome! Your ID is {client_id}',
                'timestamp': datetime.now().strftime('%H:%M:%S')
            }
            writer.write(json.dumps(welcome_msg).encode() + b'\n')
            await writer.drain()
            
            # 广播新用户加入
            await self.broadcast({
                'type': 'system',
                'content': f'User {client_id} joined the chat',
                'timestamp': datetime.now().strftime('%H:%M:%S')
            }, exclude=client_id)
            
            # 处理客户端消息
            while True:
                data = await reader.readline()
                if not data:
                    break
                    
                message = json.loads(data.decode())
                message['timestamp'] = datetime.now().strftime('%H:%M:%S')
                message['sender'] = client_id
                
                # 存储消息
                self.messages.append(message)
                
                # 广播消息
                await self.broadcast(message)
                
        except Exception as e:
            print(f"Error handling client {client_id}: {e}")
        finally:
            # 清理客户端连接
            del self.clients[client_id]
            writer.close()
            await writer.wait_closed()
            
            # 广播用户离开
            await self.broadcast({
                'type': 'system',
                'content': f'User {client_id} left the chat',
                'timestamp': datetime.now().strftime('%H:%M:%S')
            })
            print(f"Connection from {addr} closed")

    async def broadcast(self, message, exclude=None):
        """广播消息给所有客户端"""
        message_str = json.dumps(message) + '\n'
        for client_id, writer in self.clients.items():
            if client_id != exclude:
                try:
                    writer.write(message_str.encode())
                    await writer.drain()
                except Exception as e:
                    print(f"Error broadcasting to {client_id}: {e}")

    async def start(self):
        """启动服务器"""
        server = await asyncio.start_server(
            self.handle_client, self.host, self.port
        )
        
        print(f"Chat server running on {self.host}:{self.port}")
        async with server:
            await server.serve_forever()

# 客户端代码
class ChatClient:
    def __init__(self, host='localhost', port=8888):
        self.host = host
        self.port = port
        self.reader = None
        self.writer = None

    async def connect(self):
        """连接到服务器"""
        self.reader, self.writer = await asyncio.open_connection(
            self.host, self.port
        )
        print("Connected to chat server")

    async def receive_messages(self):
        """接收消息"""
        while True:
            try:
                data = await self.reader.readline()
                if not data:
                    break
                message = json.loads(data.decode())
                self.display_message(message)
            except Exception as e:
                print(f"Error receiving message: {e}")
                break

    def display_message(self, message):
        """显示消息"""
        timestamp = message['timestamp']
        if message['type'] == 'system':
            print(f"[{timestamp}] System: {message['content']}")
        else:
            sender = message['sender']
            content = message['content']
            print(f"[{timestamp}] {sender}: {content}")

    async def send_message(self, content):
        """发送消息"""
        if self.writer:
            message = {
                'type': 'message',
                'content': content
            }
            self.writer.write(json.dumps(message).encode() + b'\n')
            await self.writer.drain()

    async def run(self):
        """运行客户端"""
        await self.connect()
        
        # 启动接收消息的任务
        receive_task = asyncio.create_task(self.receive_messages())
        
        try:
            while True:
                message = input("> ")
                if message.lower() == 'quit':
                    break
                await self.send_message(message)
        except KeyboardInterrupt:
            pass
        finally:
            if self.writer:
                self.writer.close()
                await self.writer.wait_closed()
            receive_task.cancel()

# 使用示例
async def main():
    # 启动服务器
    server = ChatServer()
    server_task = asyncio.create_task(server.start())
    
    # 等待服务器启动
    await asyncio.sleep(1)
    
    # 启动客户端
    client = ChatClient()
    await client.run()
    
    # 清理
    server_task.cancel()

if __name__ == "__main__":
    asyncio.run(main())

2. 创建一个支持并发下载的下载管理器

python 复制代码
import asyncio
import aiohttp
import os
from urllib.parse import urlparse
from typing import List, Dict

class DownloadManager:
    def __init__(self, max_concurrent: int = 3):
        self.max_concurrent = max_concurrent
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.downloads: Dict[str, float] = {}  # 存储下载进度

    async def download_file(self, url: str, save_path: str = None):
        """下载单个文件"""
        if save_path is None:
            save_path = os.path.basename(urlparse(url).path)
        
        async with self.semaphore:
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(url) as response:
                        if response.status == 200:
                            total_size = int(response.headers.get('content-length', 0))
                            downloaded = 0
                            
                            with open(save_path, 'wb') as f:
                                async for chunk in response.content.iter_chunked(8192):
                                    f.write(chunk)
                                    downloaded += len(chunk)
                                    self.downloads[url] = downloaded / total_size if total_size > 0 else 1
                                    
                            print(f"Downloaded {save_path}")
                            return True
                        else:
                            print(f"Failed to download {url}: HTTP {response.status}")
                            return False
            except Exception as e:
                print(f"Error downloading {url}: {e}")
                return False

    async def download_files(self, urls: List[str], save_dir: str = None):
        """并发下载多个文件"""
        if save_dir:
            os.makedirs(save_dir, exist_ok=True)
        
        tasks = []
        for url in urls:
            save_path = os.path.join(save_dir, os.path.basename(urlparse(url).path)) if save_dir else None
            tasks.append(self.download_file(url, save_path))
        
        results = await asyncio.gather(*tasks)
        return all(results)

    def get_progress(self, url: str) -> float:
        """获取下载进度"""
        return self.downloads.get(url, 0)

# 使用示例
async def main():
    downloader = DownloadManager(max_concurrent=3)
    
    urls = [
        'http://example.com/file1.zip',
        'http://example.com/file2.zip',
        'http://example.com/file3.zip'
    ]
    
    # 开始下载
    success = await downloader.download_files(urls, save_dir='downloads')
    
    if success:
        print("All downloads completed successfully")
    else:
        print("Some downloads failed")

if __name__ == "__main__":
    asyncio.run(main())

3. 实现一个异步的任务队列

python 复制代码
import asyncio
from typing import Any, Callable, Dict, List
from datetime import datetime
import uuid

class AsyncTaskQueue:
    def __init__(self, max_workers: int = 3):
        self.max_workers = max_workers
        self.semaphore = asyncio.Semaphore(max_workers)
        self.tasks: Dict[str, asyncio.Task] = {}
        self.results: Dict[str, Any] = {}
        self.errors: Dict[str, Exception] = {}

    async def add_task(self, func: Callable, *args, **kwargs) -> str:
        """添加任务到队列"""
        task_id = str(uuid.uuid4())
        
        async def wrapped_task():
            async with self.semaphore:
                try:
                    result = await func(*args, **kwargs)
                    self.results[task_id] = result
                except Exception as e:
                    self.errors[task_id] = e
                finally:
                    del self.tasks[task_id]
        
        self.tasks[task_id] = asyncio.create_task(wrapped_task())
        return task_id

    async def get_result(self, task_id: str, timeout: float = None) -> Any:
        """获取任务结果"""
        if task_id in self.results:
            return self.results[task_id]
        
        if task_id in self.errors:
            raise self.errors[task_id]
        
        if task_id in self.tasks:
            try:
                await asyncio.wait_for(self.tasks[task_id], timeout)
                return self.results.get(task_id)
            except asyncio.TimeoutError:
                raise TimeoutError(f"Task {task_id} timed out")
        
        raise KeyError(f"Task {task_id} not found")

    async def cancel_task(self, task_id: str):
        """取消任务"""
        if task_id in self.tasks:
            self.tasks[task_id].cancel()
            try:
                await self.tasks[task_id]
            except asyncio.CancelledError:
                pass

    def get_task_status(self, task_id: str) -> str:
        """获取任务状态"""
        if task_id in self.results:
            return "completed"
        if task_id in self.errors:
            return "failed"
        if task_id in self.tasks:
            return "running"
        return "unknown"

# 使用示例
async def example_task(task_id: int, delay: float) -> str:
    """示例任务"""
    await asyncio.sleep(delay)
    return f"Task {task_id} completed at {datetime.now()}"

async def main():
    queue = AsyncTaskQueue(max_workers=2)
    
    # 添加任务
    task_ids = []
    for i in range(5):
        task_id = await queue.add_task(example_task, i, 1.0)
        task_ids.append(task_id)
        print(f"Added task {i} with ID {task_id}")
    
    # 等待所有任务完成
    for task_id in task_ids:
        try:
            result = await queue.get_result(task_id)
            print(f"Task {task_id} result: {result}")
        except Exception as e:
            print(f"Task {task_id} failed: {e}")

if __name__ == "__main__":
    asyncio.run(main())

练习解析

  1. 异步聊天服务器

    • 使用asyncio实现异步通信
    • 支持多客户端并发连接
    • 实现消息广播功能
    • 包含完整的错误处理
  2. 并发下载管理器

    • 支持并发下载多个文件
    • 实现下载进度跟踪
    • 包含错误处理和重试机制
    • 支持自定义并发数限制
  3. 异步任务队列

    • 实现任务队列管理
    • 支持任务状态跟踪
    • 提供任务取消功能
    • 包含超时处理

这些实现展示了异步编程的多种应用场景,包括:

  • 网络通信
  • 文件操作
  • 任务调度
  • 并发控制

每个实现都包含了完整的错误处理和资源管理,确保代码的健壮性和可靠性。

结语

Python的异步编程是一个强大的工具,它可以帮助我们:

  1. 提高I/O密集型应用的性能
  2. 实现高并发操作
  3. 优化资源利用
  4. 简化并发代码的编写

通过合理使用异步编程,我们可以构建出高效、可扩展的应用程序。记住,异步编程最适合处理I/O密集型任务,对于CPU密集型任务,还是应该考虑使用多进程。

练习

  1. 实现一个异步的聊天服务器
相关推荐
咖啡の猫27 分钟前
JavaScript基础-作用域链
开发语言·javascript
找不到、了37 分钟前
关于ReadWriteLock读写锁的介绍
java·开发语言·jvm
佩奇的技术笔记44 分钟前
Python入门手册:Python简介,什么是Python
开发语言·python
winfredzhang1 小时前
打造高效数据处理利器:用Python实现Excel文件智能合并工具
python·excel·合并·排序·xlsx
神秘敲码人1 小时前
Django基础(一)MVT 模式与 Django 框架
笔记·python·django
Y3174292 小时前
Python Day27 学习
python·学习·机器学习
建鼎呓语2 小时前
使用国内源加速Qt在线安装
开发语言·qt
漫谈网络2 小时前
Python 包管理工具 uv
开发语言·python·uv
fashia2 小时前
Java转Go日记(三十六):简单的分布式
开发语言·分布式·后端·zookeeper·golang·go
纪伊路上盛名在2 小时前
leetcode字符串篇【公共前缀】:14-最长公共前缀
python·算法·leetcode