Async Context Managers: async with

在某些场景下(如管理网络资源的连接建立、断开),用支持异步的上下文管理器是很方便的。

那么如何理解async with关键字?

先理解普通上下文管理器是靠魔法方法来提供功能的,如果将这个方法用coroutine函数代替呢,那就是async with的工作原理,看下伪代码:

class Connection:
    def __init__(self):
        self.host = host
        self.port = port

    async def __aenter__(self):    # 1
        self.conn = await get_conn(self.host, self.port)
        return conn
    async def __aexit__(self, exc_type, tb):    # 2
        await self.conn.close()

async with Connection('localhost', 8081) as conn:
    <do something with conn>
  1. __enter__用于同步上下文,__aenter__用于异步上下文;

  2. 同样的,用__aexit__替代__exit__,参数也相同,如果要在代码中抛出异常就填充参数。

仅在使用异步IO时使用异步上下文,如果代码中没有阻塞IO,就用普通的上下文管理器。

其实这种通过__enter__和__exit__定义上下文管理器的方式有些过时了,我们通过标准库contextlib中的@contextmanager装饰器将一个函数包装成上下文管理器,可以想到,类似的肯定还有@asynccontextmanager,不过是从3.7才有的。


contextlib

先看看在同步代码中如何使用@contextmanager

from contextlib import contextmanager

@contextmanager    # 1
def web_page(url):
    data = download_webpage(url)    # 2
    yield data
    update_stats(url)   # 3

with web_page('google.com') as data:    # 4
    process(data)   # 5
  1. 这个装饰器将生成器函数转变为上下文管理器;

  2. 这个函数调用类似网络接口调用,速度比CPU慢几个数量级,通常这个上下文管理器必须在单独的线程中运行,否则整个程序都会阻塞在这里;

  3. 这个函数调用通常是用来统计数据的,从并发的角度来看,需要知道这个函数是否涉及到IO调用,如果有则该函数也应该是阻塞的;

  4. 在这里调用上下文管理器,注意网络调用隐藏在上下文管理器的内部构造中;

  5. 这个函数可能是非阻塞(CPU处理少量计算)、半阻塞(固态硬盘等比网络IO更快)、阻塞(网络IO)、噩梦阻塞(大量CPU计算)的,这里假设它是非阻塞的。


现在看看异步下的例子。

from contextlib import asynccontextmanager

@asynccontextmanager    # 1
async def web_page(url):    # 2
    data = await download_webpage(url)  # 3
    yield data  # 4
    await update_stats(url)    # 5

async with web_page('google.com') as data:  # 6
    process(data)
  1. 新的异步装饰器;

  2. 需要这个被装饰的生成器函数用async def声明;

  3. 在前一个例子中可能会阻塞在这里,现在用await来促使loop可以在阻塞时切换到其它工作中,但要注意对于这个download_webpage函数本身,也要将其转换为与await关键字兼容的coroutine,如果无法转换,处理的方法在后面介绍;

  4. 如前一个例子,数据被提供给上下文管理器主体,通常应该在内部使用try/finally来捕获异常,同时注意,yield将函数变成生成器函数,async def将函数变成协程函数,同时调用,返回的是异步生成器函数,调用生成异步生成器,可以通过inspect库的isasyncgenfunction()isasyncgen()来判断类型;

  5. 这里假设我们将这个函数转换为coroutine,因此可以通过await调用;

  6. 上下文管理器的调用也变成异步的了。


在上述例子中,提到了download_webpage()update_stats()函数可能不是那么容易修改成async def声明的coroutine,因为异步支持需要在socket层面进行修改。

大多数场景下,代码函数都是阻塞的,也几乎不可能将这些函数修改为非阻塞的。尤其是在使用第三方库时,如requests库就完全地使用同步的调用。

为了解决这个问题,我们可以通过executor调用,来实现在异步代码中调用同步程序。

from contextlib import asynccontextmanager

@asynccontextmanager
async def web_page(url):    # 1
    loop = asyncio.get_event_loop()
    data = await loop.run_in_executor(None, download_webpage, url)  # 2
    yield data
    await loop.run_in_executor(None, update_stats, url)    # 3

async with web_page('google.com') as data:
    process(data)
  1. 在这个例子中,假设download_webpage和update_stats函数无法转换为coroutine,基于事件循环的编程中最大的错误就是阻塞了loop执行,为了解决这个问题,我们通过executor在单独的线程中运行这些同步调用,executor是作为loop本身的属性来使用的;

  2. 这里我们调用executor,其调用原型是AbstractEventLoop.run_in_executor(executor, func, *args),对executor参数传递None将使用默认的线程池;

  3. 在独立线程中运行另一个阻塞调用,必须在之前使用await,因为我们的上下文管理器是一个异步生成器,要在执行之前等待调用完成。


异步上下文管理器在asyncio编程中大量使用,所以还是有必要好好了解它们,可以通过官方文档进一步了解。

目前asyncio库在Python开发团队中仍处于活跃开发状态,并在3.7中又增加了一些重要的改进。

  1. asyncio.run(),用于作为asyncio程序的主入口;
  2. asyncio.create_task(),不需要loop即可创建task实例;
  3. AbstractEventLoop.sock_sendfile(),使用高性能的os.sendfile()接口来在TCP socket上发送文件;
  4. AbstractEventLoop.start_tls(),将现有连接升级为TLS;
  5. asyncio.Server.serve_forever(),一个创建asyncio网络服务器的简洁接口。

深入Asyncio(七)异步上下文管理器的更多相关文章

  1. asyncio之异步上下文管理器

    异步上下文管理器 前面文章我们提到了上下文管理器,但是这个上下文管理器只适用于同步代码,不能用于异步代码(async def形式),不过不用担心今天我们就来讨论在异步中如何使用上下文管理器. 特别提醒 ...

  2. python 黑魔法 ---上下文管理器(contextor)

    所谓上下文 计算机上下文(Context)对于我而言,一直是一个很抽象的名词.就像形而上一样,经常听见有人说,但是无法和现实认知世界相结合. 最直观的上下文,莫过于小学的语文课,经常会问联系上下文,推 ...

  3. (转)Python中的上下文管理器和Tornado对其的巧妙应用

    原文:https://www.binss.me/blog/the-context-manager-of-python-and-the-applications-in-tornado/ 上下文是什么? ...

  4. Python 上下文管理器模块--contextlib

    在 Python 处理文件的时候我们使用 with 关键词来进行文件的资源关闭,但是并不是只有文件操作才能使用 with 语句.今天就让我们一起学习 Python 中的上下文管理 contextlib ...

  5. Python 的上下文管理器是怎么设计的?

    花下猫语:最近,我在看 Python 3.10 版本的更新内容时,发现有一个关于上下文管理器的小更新,然后,突然发现上下文管理器的设计 PEP 竟然还没人翻译过!于是,我断断续续花了两周时间,终于把这 ...

  6. Python - Context Manager 上下文管理器

    什么是上下文管理器 官方解释... 上下文管理器是一个对象 它定义了在执行 with 语句时要建立的运行时上下文 上下文管理器处理进入和退出所需的运行时上下文以执行代码块 上下文管理器通常使用 wit ...

  7. python2.7高级编程 笔记一(Python中的with语句与上下文管理器学习总结)

    0.关于上下文管理器上下文管理器是可以在with语句中使用,拥有__enter__和__exit__方法的对象. with manager as var: do_something(var) 相当于以 ...

  8. 翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器

    原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...

  9. python学习笔记4(对象/引用;多范式; 上下文管理器)

    ### Python的强大很大一部分原因在于,它提供有很多已经写好的,可以现成用的对象 21. 动态类型:对象/引用 对象和引用: 对象是储存在内存中的实体,对象名只是指向这一对象的引用(refere ...

随机推荐

  1. 关于eclipse总是出现adb refused request的问题(转)

    1.检查下是不是开启了手机助手之类2.打开进程管理器,结束所有的adb.exe 3.关闭所有的杀毒软件之类的东东4.检查USB连接线,USB口,把USB线连在电脑主机后面的USB口5.重启eclips ...

  2. glRectf(-0.5f, -0.5f, 0.5f, 0.5f)

    http://bbs.csdn.net/topics/370049656 x向右,y向上时OPENGL坐标系,z向屏幕外表示正方向(-0.5,-0.5)是左下角坐标,(0.5,0.5)是右上角坐标,, ...

  3. MSP430 G2553 寄存器列表与引脚功能

    USCI_B0 USCI_B0 发送缓冲器UCB0TXBUF 06Fh USCI_B0 接收缓冲器UCB0RXBUF 06Eh USCI_B0 状态UCB0STAT 06Dh USCI B0 I2C ...

  4. SpringMVC+Shiro权限管理(转载)

    源码 http://pan.baidu.com/s/1pJzG4t1 SpringMVC+Shiro权限管理 博文目录 权限的简单描述 实例表结构及内容及POJO Shiro-pom.xml Shir ...

  5. html中常用的标签小结

    内容详细标签: <h1>~<h6>标题标签<pre>格式化文本<u>下划线(underline)<i>斜体字(italics)<cit ...

  6. 在PythonAnyWhere上部署Django项目

    http://www.jianshu.com/p/91047e3a4ee9 将项目放到git上,然后将pathonanywhere上的ssh传到git上,没有的话先创建,然后从git上把项目拷贝到pa ...

  7. 【GLSL教程】(九)其他说明 【转】

    http://blog.csdn.net/racehorse/article/details/6664775 法线矩阵 在很多顶点shader中都用到了gl_NormalMatrix.这里将介绍这个矩 ...

  8. Aliyun-CentOS7.3 Init

    Aliyun-CentOS7.3 Init 一.概述 查看系统版本 $ cat /etc/redhat-release $ uname -a 修改主机名 $ vi /etc/hostname $ re ...

  9. 谈 API 的撰写 - 总览

    背景 之前团队主要的工作就是做一套 REST API.我接手这个工作时发现那些API写的比较业余,没有考虑几个基础的HTTP/1.1 RFC(2616,7232,5988等等)的实现,于是我花了些时间 ...

  10. 手把手教你画AndroidK线分时图及指标

    先废话一下:来到公司之前.项目是由外包公司做的,面试初,没有接触过分时图k线这块,认为好难,我能搞定不.可是一段时间之后,发现之前做的那是一片稀烂,可是这货是主功能啊.迟早的自己操刀,痛下决心,开搞, ...