HTTP请求完成之前,asyncio似乎已经完成

时间:2019-07-09 15:37:21

标签: python-3.x google-cloud-functions python-asyncio

我尝试使用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的调用。

1 个答案:

答案 0 :(得分:1)

您所显示的代码似乎运行正常。

为了进行验证,我只对代码进行了最小程度的修改,将其与一些固定URL一起使用,这些URL仅在延迟时返回常数。然后它按预期工作。

因此,我认为Web服务可能无法提供您期望的URL结果。为了进行验证,例如,您可以使用带有所有参数的URL之一,然后从浏览器或wget命令行程序调用它。也许它不返回预期的数字?

对于独立的测试用例,将调用三个不同的URL,这些URL返回数字40、41和42。在服务器端,延迟为1-3秒。

仅进行了一些小的更改:

  • 检查int(html)语句是否存在ValueError,例如如果查询未返回int
  • 不建议使用read_timeout,因此使用aiohttp.ClientTimeout(total=5 * 60)
  • callCF被声明为返回一个int,因此逗号被删除而不返回tupel

独立测试用例

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)

结果

result

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