我尝试使用asyncio
对Web服务(由GET查询字符串格式化)运行许多并行调用,并且所有调用均立即返回值0。WebService是一个物理模拟,返回整数表现如何的价值。但是,我希望该服务运行约2分钟,然后返回该值,但是从asyncio
打印出这些值会立即显示0值。
此代码是遗传算法(运行DEAP)的一部分,所以我要做的是有一个外部(世代)循环运行,每个人(构造URL)并行运行并执行评估。我不确定这是否有影响,但这是Google Cloud Function。最大执行时间设置在预期评估时间的范围内,最大内存也有效。
这是我的asyncio
函数,如果响应返回OK,我希望在其中看到一些有效的整数值;如果生成错误,则希望看到-999:
# AsyncIO functions for interacting with Google Cloud Functions
async def fetchCF(indv: str, session: ClientSession, **kwargs) -> str:
resp = await session.request(method="GET", url=indv, **kwargs)
resp.raise_for_status()
html = await resp.text()
return html
# Dispatch the CFs and return fitness
async def callCF(indv: str, session: ClientSession, **kwargs) -> int:#float:
try:
html = await fetchCF(indv=indv, session=session, **kwargs)
except (
aiohttp.ClientError,
aiohttp.http_exceptions.HttpProcessingError,
) as e:
print(indv,e)
return -9999,
else:
return int(html),
# Called via GA
async def evalAsync(pop: set, **kwargs) -> None:
async with ClientSession(read_timeout=None) as session:
tasks = []
for p in pop:
#print(p)
indv_url = '%s?seed=%d&x0=%d&y0=%d&x1=%d&y1=%d&x2=%d&y2=%d&x3=%d&y3=%d&emitX=%d&emitY=%d' % \
(url,args.seed,int(p[0]),int(p[1]),int(p[2]),int(p[3]),int(p[4]),int(p[5]),int(p[6]),int(p[7]),int(p[8]),int(p[9]))
tasks.append(
callCF(indv=indv_url,session=session,**kwargs)
)
return await asyncio.gather(*tasks)
这是我在世代循环中如何称呼它们:
for g in generations:
...
fitnesses = asyncio.run(evalAsync(population))
作为参考,该代码在本地物理仿真中工作正常,在该仿真中,我用对本地物理驱动程序的asyncio.run
调用代替了对pool.map
的调用。
答案 0 :(得分:1)
您所显示的代码似乎运行正常。
为了进行验证,我只对代码进行了最小程度的修改,将其与一些固定URL一起使用,这些URL仅在延迟时返回常数。然后它按预期工作。
因此,我认为Web服务可能无法提供您期望的URL结果。为了进行验证,例如,您可以使用带有所有参数的URL之一,然后从浏览器或wget命令行程序调用它。也许它不返回预期的数字?
对于独立的测试用例,将调用三个不同的URL,这些URL返回数字40、41和42。在服务器端,延迟为1-3秒。
仅进行了一些小的更改:
int(html)
语句是否存在ValueError,例如如果查询未返回int aiohttp.ClientTimeout(total=5 * 60)
独立测试用例
import asyncio
import aiohttp
from aiohttp import ClientSession
async def fetchCF(indv: str, session: ClientSession, **kwargs) -> str:
resp = await session.request(method="GET", url=indv, **kwargs)
resp.raise_for_status()
html = await resp.text()
return html
# Dispatch the CFs and return fitness
async def callCF(indv: str, session: ClientSession, **kwargs) -> int:
try:
html = await fetchCF(indv=indv, session=session, **kwargs)
result = int(html)
except (
aiohttp.ClientError,
ValueError
) as e:
print(indv, e)
return -9999
else:
return result
# Called via GA
async def evalAsync(pop: set, **kwargs) -> None:
timeout = aiohttp.ClientTimeout(total=5 * 60)
async with ClientSession(timeout=timeout) as session:
tasks = []
for indv_url in pop:
tasks.append(
callCF(indv=indv_url, session=session, **kwargs)
)
return await asyncio.gather(*tasks)
if __name__ == "__main__":
population = {"https://www.software7.biz/tst/number.php",
"https://www.software7.biz/tst/number1.php",
"https://www.software7.biz/tst/number2.php"}
fitnesses = asyncio.run(evalAsync(pop=population))
for fit in fitnesses:
print(fit)
结果
Google Cloud Function
由于它可以与传统的Web服务器一起使用,因此我们现在可以尝试使用Google Cloud Functions进行简单的测试。上面的代码无需身份验证即可工作。但是添加简单的基本身份验证当然不会受到伤害。然后,服务器端的Google Cloud Function将如下所示:
import time
from flask import Flask, request
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
users = {
"someUser": "someSecret",
}
@auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
app = Flask(__name__)
@app.route('/', methods=['GET'])
@auth.login_required
def compute42(request):
op1 = request.args.get('op1')
op2 = request.args.get('op2')
time.sleep(25)
return str(int(op1) + int(op2))
它需要两个操作数,睡眠25秒,然后返回其总和。
适用于Python程序
对于此Cloud Function,必须稍微修改调用Python程序:
async def evalAsync(pop: set, **kwargs) -> None:
timeout = aiohttp.ClientTimeout(total=5 * 60)
auth = aiohttp.BasicAuth(login='someUser', password='someSecret')
async with ClientSession(timeout=timeout, auth=auth) as session:
tasks = []
for indv_url in pop:
tasks.append(
callCF(indv=indv_url, session=session, **kwargs)
)
return await asyncio.gather(*tasks)
if __name__ == "__main__":
population = {"https://someserver.cloudfunctions.net/computation42?op1=17&op2=4",
"https://someserver.cloudfunctions.net/computation42?op1=11&op2=4700",
"https://someserver.cloudfunctions.net/computation42?op1=40&op2=2"}
fitnesses = asyncio.run(evalAsync(pop=population))
for fit in fitnesses:
print(fit)
基本上仅将aiohttp.BasicAuth
和一些GET参数添加到了URL。
Python程序到控制台的输出为:
21
4711
42