Python 3.5 में coroutine और भविष्य / कार्य के बीच अंतर?


102

मान लें कि हमारे पास एक डमी फ़ंक्शन है:

async def foo(arg):
    result = await some_remote_call(arg)
    return result.upper()

के बीच क्या अंतर है:

import asyncio    

coros = []
for i in range(5):
    coros.append(foo(i))

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))

तथा:

import asyncio

futures = []
for i in range(5):
    futures.append(asyncio.ensure_future(foo(i)))

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))

नोट : उदाहरण एक परिणाम देता है, लेकिन यह सवाल का फोकस नहीं है। जब वापसी मूल्य मायने रखता है, के gather()बजाय का उपयोग करें wait()

वापसी मूल्य के बावजूद, मैं स्पष्टता की तलाश कर रहा हूं ensure_future()wait(coros)और wait(futures)दोनों ही कोरआउट्स चलाते हैं, इसलिए कोरआउट कब और क्यों लपेटा जाना चाहिए ensure_future?

मूल रूप से, पायथन 3.5 के उपयोग से नॉन-ब्लॉकिंग ऑपरेशन का एक गुच्छा चलाने के लिए राइट वे (टीएम) asyncक्या है?

अतिरिक्त क्रेडिट के लिए, क्या होगा यदि मैं कॉल को बैचना चाहता हूं? उदाहरण के लिए, मुझे some_remote_call(...)1000 बार कॉल करने की आवश्यकता है , लेकिन मैं 1000 एक साथ कनेक्शन के साथ वेब सर्वर / डेटाबेस / आदि को क्रश नहीं करना चाहता। यह एक थ्रेड या प्रोसेस पूल के साथ उल्लेखनीय है, लेकिन क्या ऐसा करने का कोई तरीका है asyncio?

2020 अपडेट (पायथन 3.7+) : इन स्निपेट्स का उपयोग न करें। इसके बजाय उपयोग करें:

import asyncio

async def do_something_async():
    tasks = []
    for i in range(5):
        tasks.append(asyncio.create_task(foo(i)))
    await asyncio.gather(*tasks)

def do_something():
    asyncio.run(do_something_async)

तीनों का उपयोग करने पर भी विचार करें , एसिंसीओ के लिए एक मजबूत 3 पार्टी विकल्प।

जवाबों:


96

एक कोरआउट एक जनरेटर फ़ंक्शन है जो मूल्यों को प्राप्त कर सकता है और बाहर से मूल्यों को स्वीकार कर सकता है। एक कोरटाइन का उपयोग करने का लाभ यह है कि हम किसी फ़ंक्शन के निष्पादन को रोक सकते हैं और इसे बाद में फिर से शुरू कर सकते हैं। नेटवर्क ऑपरेशन के मामले में, जब हम प्रतिक्रिया की प्रतीक्षा कर रहे होते हैं, तो यह किसी फ़ंक्शन के निष्पादन को रोक देता है। हम कुछ अन्य कार्यों को चलाने के लिए समय का उपयोग कर सकते हैं।

एक भविष्य Promiseजावास्क्रिप्ट से वस्तुओं की तरह है। यह मूल्य के लिए एक प्लेसहोल्डर की तरह है जो भविष्य में भौतिक होगा। नेटवर्क I / O पर प्रतीक्षा करते समय उपर्युक्त मामले में, एक फ़ंक्शन हमें एक कंटेनर दे सकता है, यह वादा करता है कि यह ऑपरेशन पूरा होने पर कंटेनर को मूल्य के साथ भर देगा। हम भविष्य की वस्तु को पकड़ते हैं और जब यह पूरा हो जाता है, तो हम वास्तविक परिणाम प्राप्त करने के लिए उस पर एक विधि कह सकते हैं।

प्रत्यक्ष उत्तर:ensure_future यदि आपको परिणामों की आवश्यकता नहीं है, तो आपको इसकी आवश्यकता नहीं है। वे अच्छे हैं यदि आपको परिणामों की आवश्यकता है या अपवाद प्राप्त हुआ है।

अतिरिक्त क्रेडिट: मैं अधिकतम कर्मचारियों की संख्या को नियंत्रित करने के लिए run_in_executorएक Executorउदाहरण चुनूंगा और पारित करूंगा ।

स्पष्टीकरण और नमूना कोड

पहले उदाहरण में, आप coroutines का उपयोग कर रहे हैं। waitसमारोह coroutines का एक समूह लेता है और उन्हें एक साथ जोड़ती है। इसलिए wait()जब सभी कोरआउट समाप्त हो जाते हैं (सभी मूल्यों को पूरा करते हुए पूरा / समाप्त हो जाता है)।

loop = get_event_loop() # 
loop.run_until_complete(wait(coros))

run_until_completeविधि सुनिश्चित करें कि पाश जब तक निष्पादन समाप्त हो गया है जीवित है होगा। कृपया ध्यान दें कि आपको इस मामले में एसिंक्स निष्पादन के परिणाम कैसे नहीं मिल रहे हैं।

दूसरे उदाहरण में, आप ensure_futureएक coroutine को लपेटने के लिए फ़ंक्शन का उपयोग कर रहे हैं और एक Taskऑब्जेक्ट लौटाते हैं जो एक तरह का है Future। जब आप कॉल करते हैं तो कोरआउट मुख्य इवेंट लूप में निष्पादित किया जाता है ensure_future। लौटाए गए भविष्य / कार्य ऑब्जेक्ट का अभी तक कोई मूल्य नहीं है, लेकिन समय के साथ, जब नेटवर्क संचालन समाप्त हो जाता है, तो भविष्य की वस्तु ऑपरेशन के परिणाम को धारण करेगी।

from asyncio import ensure_future

futures = []
for i in range(5):
    futures.append(ensure_future(foo(i)))

loop = get_event_loop()
loop.run_until_complete(wait(futures))

इस उदाहरण में, हम एक ही काम कर रहे हैं सिवाय इसके कि हम वायदा का उपयोग कर रहे हैं केवल कॉरटाइन का उपयोग करने के बजाय।

आइए एक उदाहरण देखें कि एसिंको / कोराउटाइन / फ्यूचर का उपयोग कैसे करें:

import asyncio


async def slow_operation():
    await asyncio.sleep(1)
    return 'Future is done!'


def got_result(future):
    print(future.result())

    # We have result, so let's stop
    loop.stop()


loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)

# We run forever
loop.run_forever()

यहां, हमने ऑब्जेक्ट create_taskपर विधि का उपयोग किया है loopensure_futureमुख्य ईवेंट लूप में कार्य शेड्यूल करेगा। यह विधि हमें चुनने वाले लूप पर एक कोरटाइन शेड्यूल करने में सक्षम बनाती है।

हम add_done_callbackटास्क ऑब्जेक्ट पर विधि का उपयोग करके कॉलबैक जोड़ने की अवधारणा भी देखते हैं ।

एक Taskहै doneजब coroutine एक मान देता है, एक अपवाद को जन्म देती है या रद्द कर दिया जाता है। इन घटनाओं की जांच करने के तरीके हैं।

मैंने इन विषयों पर कुछ ब्लॉग पोस्ट लिखी हैं जिनसे मदद मिल सकती है:

बेशक, आप आधिकारिक मैनुअल पर अधिक जानकारी पा सकते हैं: https://docs.python.org/3/library/asyncio.html


3
मैंने अपने प्रश्न को थोड़ा और स्पष्ट करने के लिए अपडेट किया है - अगर मुझे कोरआउट से परिणाम की आवश्यकता नहीं है, तो क्या मुझे अभी भी उपयोग करने की आवश्यकता है ensure_future()? और अगर मुझे परिणाम की आवश्यकता है, तो क्या मैं अभी उपयोग नहीं कर सकता हूं run_until_complete(gather(coros))?
नाइट

1
ensure_futureईवेंट लूप में निष्पादित किए जाने वाले कोआउटिन को शेड्यूल करता है। तो मैं कहूँगा हाँ, यह आवश्यक है। लेकिन निश्चित रूप से आप अन्य कार्यों / विधियों का उपयोग करके भी कोआउट कर सकते हैं। हां, आप उपयोग कर सकते हैं gather()- लेकिन तब तक इंतजार करेंगे जब तक कि सभी प्रतिक्रियाएं एकत्र न हो जाएं।
मासुना

5
@AbuAshrafMasnun @knite gatherऔर waitवास्तव में उपयोग किए गए कार्यों के रूप में दिए गए कोरआउट को लपेटें ensure_future( यहां और यहां स्रोत देखें )। तो ensure_futureपहले से उपयोग करने का कोई मतलब नहीं है , और इसका परिणाम पाने या न होने से कोई लेना-देना नहीं है।
विंसेंट

8
@AbuAshrafMasnun इसके अलावा @knite, ensure_futureएक है loopतर्क, इसलिए वहाँ का उपयोग करने का कोई कारण नहीं है loop.create_taskसे अधिक ensure_future। और run_in_executorcoroutines के साथ काम नहीं करेगा, इसके बजाय एक सेमाफोर का उपयोग किया जाना चाहिए।
विन्सेंट

2
@vincent वहाँ उपयोग करने के लिए एक कारण है create_taskके ऊपर ensure_future, देखने डॉक्स । उद्धरणcreate_task() (added in Python 3.7) is the preferable way for spawning new tasks.
मासी

24

सरल उत्तर

  • एक coroutine फ़ंक्शन ( async def) को चलाने से यह नहीं चलता है। यह एक coroutine ऑब्जेक्ट देता है, जैसे जनरेटर फ़ंक्शन, जनरेटर ऑब्जेक्ट लौटाता है।
  • await कॉरआउट्स से मानों को पुनः प्राप्त करता है, अर्थात कॉरआउट को "कॉल" करता है
  • eusure_future/create_task अगली यात्रा पर ईवेंट लूप पर चलने के लिए कोरटाइन को शेड्यूल करें (हालांकि, डेमन थ्रेड की तरह उन्हें समाप्त करने के लिए इंतजार नहीं करना चाहिए)।

कुछ कोड उदाहरण

आइए पहले कुछ शब्द स्पष्ट करें:

  • coroutine फ़ंक्शन, वह जिसे आप चाहते हैं async def;
  • coroutine ऑब्जेक्ट, जब आपने एक coroutine फ़ंक्शन को "कॉल" किया तो आपको क्या मिला;
  • कार्य, ईवेंट लूप पर चलने के लिए एक coroutine ऑब्जेक्ट के चारों ओर लिपटा हुआ ऑब्जेक्ट।

केस 1, awaitएक कोरटाइन पर

हम दो coroutines बनाते हैं, awaitएक, और create_taskदूसरे को चलाने के लिए उपयोग करते हैं।

import asyncio
import time

# coroutine function
async def p(word):
    print(f'{time.time()} - {word}')


async def main():
    loop = asyncio.get_event_loop()
    coro = p('await')  # coroutine
    task2 = loop.create_task(p('create_task'))  # <- runs in next iteration
    await coro  # <-- run directly
    await task2

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

आपको परिणाम मिलेगा:

1539486251.7055213 - await
1539486251.7055705 - create_task

के बारे में बताएं:

task1 को सीधे निष्पादित किया गया था, और task2 को निम्नलिखित पुनरावृत्ति में निष्पादित किया गया था।

केस 2, इवेंट लूप पर पैदावार नियंत्रण

यदि हम मुख्य कार्य को प्रतिस्थापित करते हैं, तो हम एक अलग परिणाम देख सकते हैं:

async def main():
    loop = asyncio.get_event_loop()
    coro = p('await')
    task2 = loop.create_task(p('create_task'))  # scheduled to next iteration
    await asyncio.sleep(1)  # loop got control, and runs task2
    await coro  # run coro
    await task2

आपको परिणाम मिलेगा:

-> % python coro.py
1539486378.5244057 - create_task
1539486379.5252144 - await  # note the delay

के बारे में बताएं:

जब फोन किया जाता है asyncio.sleep(1), तो कंट्रोल को इवेंट लूप में वापस कर दिया जाता है, और लूप को चलाने के लिए कार्यों की जाँच करता है, फिर यह द्वारा बनाए गए कार्य को चलाता है create_task

ध्यान दें कि, हम पहले coroutine फ़ंक्शन को लागू करते हैं, लेकिन awaitयह नहीं , इसलिए हमने केवल एक coroutine बनाया, और इसे चालू नहीं किया। फिर, हम create_taskकोरटाइन फ़ंक्शन को फिर से कॉल करते हैं, और इसे कॉल में लपेटते हैं, creat_task वास्तव में अगली यात्रा पर चलने के लिए कोरटाइन शेड्यूल करेगा। इसलिए, परिणाम में, create taskपहले निष्पादित किया जाता है await

दरअसल, यहाँ बिंदु पाश को वापस नियंत्रण देना है, आप asyncio.sleep(0)उसी परिणाम को देखने के लिए उपयोग कर सकते हैं ।

हुड के नीचे

loop.create_taskवास्तव में कॉल asyncio.tasks.Task(), जो कॉल करेगा loop.call_soon। और loop.call_soonटास्क को अंदर कर देगा loop._ready। लूप के प्रत्येक पुनरावृत्ति के दौरान, यह लूप में प्रत्येक कॉलबैक की जांच करता है। पहले से ही और इसे चलाता है।

asyncio.wait, asyncio.ensure_futureऔर asyncio.gatherवास्तव में loop.create_taskप्रत्यक्ष या अप्रत्यक्ष रूप से कहते हैं।

डॉक्स में भी ध्यान दें :

कॉलबैक उस क्रम में कहा जाता है जिसमें वे पंजीकृत हैं। प्रत्येक कॉलबैक को एक बार बिल्कुल कॉल किया जाएगा।


1
स्वच्छ स्पष्टीकरण के लिए धन्यवाद! कहना है, यह एक बहुत ही भयानक डिजाइन है। उच्च-स्तरीय API निम्न-स्तर की अमूर्तता को लीक कर रहा है, जो API को ओवरप्लेक्ट करता है।
बोरिस बुर्कोव

1
क्यूरियो प्रोजेक्ट देखें, जो अच्छी तरह से डिजाइन किया गया है
ospider

अच्छी व्याख्या! मुझे लगता है कि await task2कॉल के प्रभाव को स्पष्ट किया जा सकता है। दोनों उदाहरणों में, loop.create_task () कॉल है जो इवेंट लूप पर कार्य 2 को शेड्यूल करता है। तो दोनों एक्स में आप डिलीट कर सकते हैं await task2और फिर भी टास्क 2 आखिरकार चलेगा। Ex2 में व्यवहार समान होगा, जैसा कि await task2मेरा मानना ​​है कि पहले से ही पूर्ण किए गए कार्य को शेड्यूल कर रहा है (जो दूसरी बार नहीं चलेगा), जबकि ex1 में व्यवहार थोड़ा भिन्न होगा क्योंकि मुख्य पूरा होने तक कार्य निष्पादित नहीं किया जाएगा। अंतर देखने के लिए, print("end of main")ex1 के मुख्य के अंत में जोड़ें
एंड्रयू

11

विन्सेन्ट की एक टिप्पणी https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 से जुड़ी है , जो बताती है कि आपके लिए wait()कॉरटाइन्स को लपेटता है ensure_future()!

दूसरे शब्दों में, हमें भविष्य की आवश्यकता है, और कोरटाइन चुपचाप उन में बदल जाएंगे।

मैं इस उत्तर को अपडेट करूंगा जब मुझे कोराउटिन्स / फ्यूचर्स को बैचने का एक निश्चित विवरण मिलेगा।


इसका मतलब यह है कि एक coroutine ऑब्जेक्ट के लिए c, await cके बराबर है await create_task(c)?
एलेक्सी

3

BDFL से [2013]

कार्य

  • यह एक भविष्य में लिपटे एक कोरटाइन है
  • क्लास टास्क क्लास फ्यूचर का एक उपवर्ग है
  • तो यह इंतजार के साथ भी काम करता है!

  • यह नंगे कोरआउट से कैसे भिन्न होता है?
  • इसकी प्रतीक्षा किए बिना वह प्रगति कर सकता है
    • जब तक आप किसी और चीज की प्रतीक्षा करते हैं, यानी
      • प्रतीक्षा करें [something_else]

इसे ध्यान में रखते हुए, ensure_futureटास्क बनाने के लिए एक नाम के रूप में समझ में आता है क्योंकि भविष्य के परिणाम की गणना की जाएगी या नहीं जब तक आप इसका इंतजार नहीं करते (जब तक आप कुछ इंतजार करते हैं)। यह इवेंट लूप को आपके कार्य को पूरा करने की अनुमति देता है जबकि आप अन्य चीजों पर प्रतीक्षा कर रहे हैं। ध्यान दें कि Python 3.7 create_taskएक पसंदीदा तरीका है जो भविष्य को सुनिश्चित करता है

नोट: मैंने आधुनिकता के लिए गुइडो की स्लाइड्स में "यील्ड" को "प्रतीक्षा" में बदल दिया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.