如何使用asyncio添加连接超时?

24

我想要快速连接到很多不同的网站列表。我正在使用异步编程(asyncio)以异步方式进行此操作,现在希望添加一些超时设置,以便在连接等待时间过长时忽略这些连接。

我该如何实现这个功能?

import ssl
import asyncio
from contextlib import suppress
from concurrent.futures import ThreadPoolExecutor
import time


@asyncio.coroutine
def run():
    while True:
        host = yield from q.get()
        if not host:
            break

        with suppress(ssl.CertificateError):
            reader, writer = yield from asyncio.open_connection(host[1], 443, ssl=True) #timout option?
            reader.close()
            writer.close()


@asyncio.coroutine
def load_q():
    # only 3 entries for debugging reasons
    for host in [[1, 'python.org'], [2, 'qq.com'], [3, 'google.com']]:
        yield from q.put(host)
    for _ in range(NUM):
        q.put(None)


if __name__ == "__main__":
    NUM = 1000
    q = asyncio.Queue()

    loop = asyncio.get_event_loop()
    loop.set_default_executor(ThreadPoolExecutor(NUM))

    start = time.time()
    coros = [asyncio.async(run()) for i in range(NUM)]
    loop.run_until_complete(load_q())
    loop.run_until_complete(asyncio.wait(coros))
    end = time.time()
    print(end-start)

(顺便说一句:有人知道如何优化这个吗?)


你忘记在load_q函数中的对q.put(None)的调用上使用yield from,因此当前代码无法正常工作。 - dano
你不需要reader和writer。你可以使用 asyncio.create_connectionProtocol(它在建立连接后立即关闭网络连接)。这是一个代码示例,我在百万Alexa站点列表上尝试过(可能稍微有些过时,例如没有使用一些方便函数,如 asyncio.wait_for())。它只使用一个线程并最多打开 limit 个ssl连接。 - jfs
1个回答

28
你可以将对`open_connection`的调用封装在asyncio.wait_for中,这样可以指定超时时间。
    with suppress(ssl.CertificateError):
        fut = asyncio.open_connection(host[1], 443, ssl=True)
        try:
            # Wait for 3 seconds, then raise TimeoutError
            reader, writer = yield from asyncio.wait_for(fut, timeout=3)
        except asyncio.TimeoutError:
            print("Timeout, skipping {}".format(host[1]))
            continue

注意,当引发TimeoutError时,open_connection协程也将被取消。如果您不希望它被取消(尽管我认为在这种情况下您希望它被取消),则需要在调用中包装asyncio.shield

但这样做也会造成阻塞调用,对吧?就像在普通循环中一个接一个地打开连接。 - Ali Faizan
1
@ali 不会,因为所有对run方法的调用都被包装在asyncio.async调用中,这意味着它们都是并发运行的。 - dano
1
如果连接超时需要在另一个协程中,可以参考[https://dev59.com/214b5IYBdhLWcg3w3k2Y#48546189](Python asyncio force timeout)有关堆叠asyncio.ensure_future(asyncio.wait_for(create_connection()))的内容。 - Jari Turkia
1
我非常确定这在3.7版本中停止工作,因为wait_for文档中提到了以下更改-从3.7版本开始更改:当由于超时而取消aw时,wait_for会等待aw被取消。 以前,它会立即引发asyncio.TimeoutError异常。 - WirthLuce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接