ฉันจะใช้การร้องขอใน asyncio ได้อย่างไร?


128

ฉันต้องการทำงานคำขอ http คู่ขนานในasyncioแต่ฉันพบว่าpython-requestsจะบล็อกการวนซ้ำเหตุการณ์ของasyncio. ฉันพบaiohttpแต่ไม่สามารถให้บริการคำขอ http โดยใช้พร็อกซี http

ดังนั้นฉันต้องการทราบว่ามีวิธีทำคำขอ http แบบอะซิงโครนัสด้วยความช่วยเหลือของasyncio.


1
หากคุณเพียงแค่ส่งคำขอคุณสามารถใช้subprocessเพื่อขนานรหัสของคุณได้
WeaselFox

วิธีนี้ดูเหมือนจะไม่สวยหรู……
ใบปลิว

ขณะนี้มีพอร์ตคำขอ asyncio github.com/rdbhost/yieldfromRequests
Rdbhost

คำตอบ:


183

ในการใช้คำร้องขอ (หรือไลบรารีการบล็อกอื่น ๆ ) กับ asyncio คุณสามารถใช้BaseEventLoop.run_in_executorเพื่อรันฟังก์ชันในเธรดอื่นและให้ผลลัพธ์จากมันเพื่อให้ได้ผลลัพธ์ ตัวอย่างเช่น:

import asyncio
import requests

@asyncio.coroutine
def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = yield from future1
    response2 = yield from future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

สิ่งนี้จะได้รับคำตอบทั้งสองแบบพร้อมกัน

ด้วย python 3.5 คุณสามารถใช้ new await/ asyncsyntax:

import asyncio
import requests

async def main():
    loop = asyncio.get_event_loop()
    future1 = loop.run_in_executor(None, requests.get, 'http://www.google.com')
    future2 = loop.run_in_executor(None, requests.get, 'http://www.google.co.uk')
    response1 = await future1
    response2 = await future2
    print(response1.text)
    print(response2.text)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

ดูPEP0492สำหรับข้อมูลเพิ่มเติม


5
คุณช่วยอธิบายได้ไหมว่ามันทำงานอย่างไร ฉันไม่เข้าใจว่าสิ่งนี้ไม่ปิดกั้นได้อย่างไร
Scott Coates

32
@christian แต่ถ้ามันทำงานพร้อมกันในเธรดอื่นนั่นไม่ใช่การเอาชนะจุด asyncio หรือ?
Scott Coates

21
@scoarescoare นั่นคือส่วนที่ 'ถ้าคุณทำถูกต้อง' เข้ามา - วิธีการที่คุณเรียกใช้ในตัวดำเนินการควรมีอยู่ในตัว ((ส่วนใหญ่) เหมือนการร้องขอรับในตัวอย่างด้านบน) ด้วยวิธีนี้คุณไม่ต้องจัดการกับหน่วยความจำที่ใช้ร่วมกันการล็อก ฯลฯ และส่วนที่ซับซ้อนของโปรแกรมของคุณยังคงเป็นเธรดเดียวด้วย asyncio
christian

5
@scoarescoare กรณีการใช้งานหลักมีไว้สำหรับการผสานรวมกับไลบรารี IO ที่ไม่รองรับ asyncio ตัวอย่างเช่นฉันกำลังทำงานกับอินเทอร์เฟซ SOAP ที่เก่าแก่อย่างแท้จริงและฉันใช้ไลบรารี suds-Jurko เป็นโซลูชันที่ "แย่น้อยที่สุด" ฉันกำลังพยายามรวมเข้ากับเซิร์ฟเวอร์ asyncio ดังนั้นฉันจึงใช้ run_in_executor เพื่อทำการบล็อคการโทรในลักษณะที่ดูไม่ตรงกัน
Lucretiel

10
เจ๋งมากที่ใช้งานได้และง่ายมากสำหรับของเดิม แต่ควรเน้นว่าสิ่งนี้ใช้เธรดพูลระบบปฏิบัติการดังนั้นจึงไม่ปรับขนาดเป็น lib เชิง asyncio ที่แท้จริงเหมือนที่ aiohttp ทำ
jsalter

78

aiohttpสามารถใช้กับพร็อกซี HTTP ได้แล้ว:

import asyncio
import aiohttp


@asyncio.coroutine
def do_request():
    proxy_url = 'http://localhost:8118'  # your proxy address
    response = yield from aiohttp.request(
        'GET', 'http://google.com',
        proxy=proxy_url,
    )
    return response

loop = asyncio.get_event_loop()
loop.run_until_complete(do_request())

ตัวเชื่อมต่อทำอะไรที่นี่?
Markus Meskanen

ให้การเชื่อมต่อผ่านพร็อกซีเซิร์ฟเวอร์
mindmaster

16
นี่เป็นวิธีที่ดีกว่ามากในการใช้การร้องขอในเธรดแยกต่างหาก เนื่องจากเป็นแบบ async อย่างแท้จริงจึงมีค่าใช้จ่ายที่ต่ำกว่าและการใช้งาน mem ลดลง
ทม

14
สำหรับ python> = 3.5 แทนที่ @ asyncio.coroutine ด้วย "async" และ "yield from" ด้วย "await"
James

41

คำตอบข้างต้นยังคงใช้โครูทีนสไตล์ Python 3.4 แบบเก่า นี่คือสิ่งที่คุณจะเขียนหากคุณมี Python 3.5+

aiohttp รองรับ http proxy ทันที

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
            'http://python.org',
            'https://google.com',
            'http://yifei.me'
        ]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))
        htmls = await asyncio.gather(*tasks)
        for html in htmls:
            print(html[:100])

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

1
คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับ URL เพิ่มเติมได้ไหม ไม่สมเหตุสมผลที่จะมีเพียง URL เดียวเมื่อคำถามเกี่ยวกับคำขอ http คู่ขนาน
ไม่ระบุชื่อ

ตำนาน. ขอบคุณ! ใช้งานได้ดี
อดัม

@ospider โค้ดนี้สามารถแก้ไขได้อย่างไรเพื่อส่ง URL ที่พูด 10k โดยใช้ 100 คำขอพร้อมกัน? แนวคิดคือการใช้ช่องทั้งหมด 100 ช่องพร้อมกันไม่ใช่รอให้ส่ง 100 ช่องเพื่อเริ่ม 100 ช่องถัดไป
Antoan Milkov

@AntoanMilkov นั่นเป็นคำถามอื่นที่ไม่สามารถตอบได้ในพื้นที่แสดงความคิดเห็น
ospider

@ospider คุณพูดถูกคำถามนี้stackoverflow.com/questions/56523043/…
Antoan Milkov

11

ขณะนี้คำขอไม่รองรับasyncioและไม่มีแผนที่จะให้การสนับสนุนดังกล่าว เป็นไปได้ว่าคุณสามารถใช้กำหนดเอง "การขนส่งอะแดปเตอร์" (ตามที่กล่าวไว้ที่นี่ ) asyncioที่รู้วิธีการใช้งาน

ถ้าฉันพบว่าตัวเองมีบางครั้งก็เป็นสิ่งที่ฉันอาจมองเข้าไป แต่ฉันไม่สามารถสัญญาอะไรได้


ลิงก์นำไปสู่ ​​404
CodeBiker

8

มีกรณีที่ดีของ async / await ลูปและเธรดในบทความโดย Pimin Konstantin Kefaloukos คำขอ HTTP แบบขนานอย่างง่ายด้วย Python และ asyncio :

เพื่อลดเวลาในการดำเนินการทั้งหมดให้น้อยที่สุดเราสามารถเพิ่มขนาดของเธรดพูลเพื่อให้ตรงกับจำนวนคำขอที่เราต้องทำ โชคดีที่นี่เป็นเรื่องง่ายที่จะทำอย่างที่เราจะเห็นต่อไป รายการโค้ดด้านล่างเป็นตัวอย่างวิธีสร้างคำร้องขอ HTTP แบบอะซิงโครนัสยี่สิบรายการโดยมีเธรดพูลของเธรดผู้ปฏิบัติงานยี่สิบเธรด:

# Example 3: asynchronous requests with larger thread pool
import asyncio
import concurrent.futures
import requests

async def main():

    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:

        loop = asyncio.get_event_loop()
        futures = [
            loop.run_in_executor(
                executor, 
                requests.get, 
                'http://example.org/'
            )
            for i in range(20)
        ]
        for response in await asyncio.gather(*futures):
            pass


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

2
ปัญหานี้คือถ้าฉันต้องการเรียกใช้คำขอ 10,000 รายการโดยมีตัวดำเนินการ 20 ตัวฉันต้องรอให้ตัวดำเนินการทั้ง 20 ตัวดำเนินการเสร็จสิ้นเพื่อที่จะเริ่มต้นด้วย 20 รายการถัดไปใช่ไหม ฉันทำไม่ได้for i in range(10000)เพราะคำขอหนึ่งอาจล้มเหลวหรือหมดเวลาใช่ไหม
Sanandrea

1
คุณช่วยอธิบายได้ไหมว่าทำไมคุณถึงต้องใช้ asyncio ในเมื่อคุณสามารถทำเช่นเดียวกันได้โดยใช้ ThreadPoolExecutor?
Asaf Pinhassi

@lya Rusin เรากำหนดจำนวน max_workers หรือไม่? ต้องเกี่ยวข้องกับจำนวน CPU และเธรดหรือไม่?
alt-f4
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.