·  阅读 1072

# 异步基础

## 异步

``````#coding:gbk

import time

def water():
print("开始烧水了")
start = time.time()
time.sleep(2)
end = time.time()
return ("水开了", end - start)

def wash():
print("开始洗衣服了")
start = time.time()
time.sleep(4)
end = time.time()
return ("衣服洗完了", end - start)

def cook():
print("开始做饭了")
start = time.time()
time.sleep(10)
end = time.time()
return ("饭熟了",end - start)

def alarm(info):
print("%s 一共用了%s 时间"%(info[0],info[1]))

def __init__(self, func, callback,*params, **paramMap):
self.func = func
self.params = params
self.paramMap = paramMap
self.rst = None
self.finished = False
self.isDaemon = True
self.isDaemon = False
self.callback = callback

def run(self):
self.rst = self.func(*self.params, **self.paramMap)
self.callback(self.rst)
self.finished = True

if __name__ == '__main__':

start = time.time()
end = time.time()
print("一共用了%s时间"%(end-start))

``````开始烧水了

## `yield` 语法

``````def h():
print('I am yangyanxing')
yield 5

if __name__ == '__main__':
c = h()

``````def h():
print('I am yangyanxing')
yield 5
print("I am fjy")

if __name__ == '__main__':
c = h()
c.next()

`next()` 函数相关的还有一个 `send()` 函数，`next` 函数传递的是 `None``send` 函数可以传递对应的值。其实next()和send()在一定意义上作用是相似的，区别是send()可以传递yield表达式的值进去，而next()不能传递特定的值，只能传递None进去。因此，我们可以看做
c.next() 和 c.send(None) 作用是一样的。看如下的代码。

``````def h():
print('I am yangyanxing')
m = yield 5
print(m)
print("I am fjy")
yield 6
print("They love too much")

if __name__ == '__main__':
c = h()
c.next()
c.send("hahaha")

``````I am yangyanxing
hahaha
I am fjy

``````def h():
print('I am yangyanxing')
m = yield 5
print(m)
print("I am fjy")
yield 6

if __name__ == '__main__':
c = h()
print(c.next())
print(c.send("hahaha"))

``````I am yangyanxing
5
hahaha
I am fjy
6

# 异步使用

## 同步的困扰

``````import tornado.httpserver
import time

define("port", default=8000, help="run on the given port", type=int)

def get(self):
query = self.get_argument('q')
time.sleep(5)
self.write("hello %s"%query)

if __name__ == "__main__":
http_server.listen(options.port)

``````import requests,time

def geturl(url):
s = time.time()
r = requests.get(url)
e = time.time()
print(int(e-s))

getrul(r"http://127.0.0.1:8000/?q=yangyanxing")
```

```
[I 190114 00:03:46 web:2162] 304 GET /?q=yangyanxing (127.0.0.1) 5000.97ms
[I 190114 00:03:51 web:2162] 200 GET /?q=yangyanxing (127.0.0.1) 5006.78ms

## 异步的使用

• 客户端的实现
异步的使用可以分为客户端的调用与服务端的处理，先从简单的来看，客户端的调用，比如你要同时访问 baidu.com 10次，你会怎么做？可以依次的对 baidu 发起10次请求，每次请求结束以后再发起下一次请求，假如每次请求是1秒钟，那么10次请求至少要用10秒钟，排除IO相关耗时，代码可能是这个样子的
``````#coding:utf-8

import time
import requests

def geturl(url):
start = time.time()
r = requests.get(url)
end = time.time()
print("用了%s时间"%(end-start))
return r.status_code

if __name__ == '__main__':
start = time.time()
for i in range(10):
geturl(r"https://www.baidu.com")
end = time.time()
print("all done,use %s time"%(end-start))

``````用了0.2974998950958252时间

all done,use 0.7105000019073486 time

``````import tornado.httpserver
import asyncio

import urllib
import json
import datetime
import time

define("port", default=8000, help="run on the given port", type=int)

async def get(self):
query = self.get_argument('q')
await asyncio.sleep(5)
self.writeres("hello %s"%query)
self.finish()

def writeres(self,returnstr):
self.write(returnstr)

if __name__ == "__main__":
http_server.listen(options.port)

``````def geturl(url):
start = time.time()
r = requests.get(url)
end = time.time()
print("用了%s时间"%(end-start))
return r.status_code

if __name__ == '__main__':
start = time.time()
for i in range(3):
geturl(r"http://127.0.0.1:8000/?q=yangyanxing")
end = time.time()
print("all done,use %s time"%(end-start))

``````用了5.009501695632935时间

all done,use 15.035006284713745 time

• 定义协程

``````async def getUrlByCor(url):
start = time.time()
r = requests.get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r.status_code

if __name__ == '__main__':
s = getUrlByCor(r'http://127.0.0.1:8000/?q=yangyanxing')
print(s)

``````<coroutine object getUrlByCor at 0x00000000034BBF10>

``````async def getUrlByCor(url):
start = time.time()
r = requests.get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r.status_code

if __name__ == '__main__':
s = getUrlByCor(r"http://127.0.0.1:8000/?q=yangyanxing")
loop = asyncio.get_event_loop()
loop.run_until_complete(s)

``````async def getUrlByCor(url):
start = time.time()
r = requests.get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r.text

if __name__ == '__main__':
s = time.time()
tasks = [asyncio.ensure_future(getUrlByCor(r"http://127.0.0.1:8000/?q=yangyanxing")) for i in range(3)]
loop = asyncio.get_event_loop()
e = time.time()
print("use %s time"%(e-s))

``````3 [<Task pending coro=<getUrlByCor() running at E:/Github/asyncTorMysql/asynctest.py:69>>, <Task pending coro=<getUrlByCor() running at E:/Github/asyncTorMysql/asynctest.py:69>>, <Task pending coro=<getUrlByCor() running at E:/Github/asyncTorMysql/asynctest.py:69>>]

use 15.03450632095337 time

`r = await requests.get(url)`

``````async def getUrlByCor(url):
start = time.time()
r = await requests.get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r.text

if __name__ == '__main__':
s = time.time()
tasks = [asyncio.ensure_future(getUrlByCor(r"http://127.0.0.1:8000/?q=yangyanxing")) for i in range(3)]
loop = asyncio.get_event_loop()
e = time.time()
print("use %s time"%(e-s))

future: exception=TypeError(“object Response can’t be used in ‘await’ expression”,)>
Traceback (most recent call last):
File “C:\Python35\lib\asyncio\tasks.py”, line 240, in _step
result = coro.send(None)
File “E:/Github/asyncTorMysql/asynctest.py”, line 71, in getUrlByCor
r = await requests.get(url)
TypeError: object Response can’t be used in ‘await’ expression

1. 原生 coroutine 对象
2. 一个由 types.coroutine() 修饰的生成器，这个生成器可以返回 coroutine 对象。
3. 一个包含 __await 方法的对象返回的一个迭代器。

reqeusts 返回的 Response 不符合上面任一条件，因此就会报上面的错误了。

``````async def get(url):
return requests.get(url)

async def getUrlByCor(url):
start = time.time()
r = await get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r.text

if __name__ == '__main__':
s = time.time()
tasks = [asyncio.ensure_future(getUrlByCor(r"http://127.0.0.1:8000/?q=yangyanxing")) for i in range(3)]
loop = asyncio.get_event_loop()
e = time.time()
print("use %s time"%(e-s))

``````用了5.0090014934539795时间

use 15.03450632095337 time

aiohttp 是一个支持异步请求的库，利用它和 asyncio 配合我们可以非常方便地实现异步请求操作。

``````async def get(url):
session = aiohttp.ClientSession()
res = await session.get(url)
result = await res.text()
await session.close()
return result

async def getUrlByCor(url):
start = time.time()
r = await get(url)
end = time.time()
print("用了%s时间" % (end - start))
return r

if __name__ == '__main__':
s = time.time()
tasks = [asyncio.ensure_future(getUrlByCor(r"http://127.0.0.1:8000/?q=yangyanxing")) for i in range(3)]
loop = asyncio.get_event_loop()
e = time.time()
print("use %s time"%(e-s))

``````用了5.006500005722046时间

use 5.008500099182129 time

``````#coding:utf-8

import time
import requests
import asyncio
import aiohttp

async def water():
print("开始烧水了")
start = time.time()
await asyncio.sleep(2) # 这里使用asyncio.sleep 异步休眠
end = time.time()
return ("水开了", end - start)

async def wash():
print("开始洗衣服了")
start = time.time()
await asyncio.sleep(4)
end = time.time()
return ("衣服洗完了", end - start)

async def cook():
print("开始做饭了")
start = time.time()
await asyncio.sleep(10)
end = time.time()
return ("饭熟了",end - start)

if __name__ == '__main__':
loop = asyncio.get_event_loop()

## 服务端的实现

``````import tornado.httpserver
import time

define("port", default=8000, help="run on the given port", type=int)

def get(self):
query = self.get_argument('q')
time.sleep(5)
self.write("hello %s"%query)

if __name__ == "__main__":
http_server.listen(options.port)

`IndexHandler` 中的 get 方法，由于当中存在了一个比较耗时的操作，`time.sleep(5)` 处理完这个请求需要卡5秒，在卡住的这段时间，tornado无法再完成别的请求，如果此时再发来一个 `/` 的请求，那么只能等待这前的请求操作结束之后再对处理新发过来的请求，如果同时有1万个请求发过来，可想而知，最后一个请求就等到猴年马月才能处理完呢……

``````import tornado.httpserver

import time

define("port", default=8000, help="run on the given port", type=int)

def get(self):
query = self.get_argument('q')
res = yield self.sleepBlock(2,query)
self.write("hello %s"%res)
self.finish()

@run_on_executor
def sleepBlock(self,sleeptime,query):
time.sleep(sleeptime)
return query

if __name__ == "__main__":
http_server.listen(options.port)

``````import tornado.httpserver
import asyncio

import time

define("port", default=8000, help="run on the given port", type=int)

def get(self):
query = self.get_argument('q')
res = yield self.sleepBlock(2,query)
self.write("hello %s"%res)
self.finish()

@run_on_executor
def sleepBlock(self,sleeptime,query):
time.sleep(sleeptime)
return query

async def get(self):
query = self.get_argument('q')
res = await self.sleepBlock(2,query)
self.write("hello %s"%res)
self.finish()

async def sleepBlock(self,sleeptime,query):
await asyncio.sleep(sleeptime) # 使用异步的sleep方法
return query

if __name__ == "__main__":
(r"/", IndexHandler),
(r"/async",asyncIndexHandler)
],settings={"debug":True})
http_server.listen(options.port)

`IndexHandler` 中的 get 方法使用了`async``await` 关键字来达到异步的处理请求，这里的`asyncio.sleep(5)` 是异步的暂停5秒，如果此处的方法涉及到无法使用异步请求的库该怎么处理，比如说我就想使用`time.sleep(5)` 则需要在线程池中运行，就像上面的`/` 路由里使用 `@run_on_executor` 中执行。

# 结语

Python3 异步协程函数async具体用法

Python天天美味(25) - 深入理解yield