के बारे में बात कर रहा है async/await
और asyncio
एक ही बात नहीं है। पहला एक मौलिक, निम्न-स्तरीय निर्माण (कॉरटाइन्स) है, जबकि बाद में इन निर्माणों का उपयोग करने वाला एक पुस्तकालय है। इसके विपरीत, एक भी अंतिम उत्तर नहीं है।
निम्नलिखित कैसे का एक सामान्य वर्णन है async/await
और asyncio
तरह पुस्तकालयों काम करते हैं। यही है, शीर्ष पर अन्य चालें हो सकती हैं (वहाँ हैं ...) लेकिन वे असंगत हैं जब तक कि आप उन्हें स्वयं नहीं बनाते हैं। अंतर तब तक नगण्य होना चाहिए जब तक कि आप पहले से ही पर्याप्त नहीं जानते कि इस तरह के प्रश्न को पूछना नहीं है।
1. अखरोट के खोल में कोराटाइन्स बनाम सबरूटीन्स
वैसे ही जैसे सबरूटीन्स (काम करता है, प्रक्रियाओं, ...), coroutines (जनरेटर, ...) कॉल स्टैक और अनुदेश सूचक का एक अमूर्त हैं: कोड के टुकड़े को क्रियान्वित करने का एक ढेर है, और हर एक अनुदेश पर है।
def
बनाम async def
का अंतर केवल स्पष्टता के लिए है। वास्तविक अंतर return
बनाम है yield
। इससे, await
या yield from
अलग-अलग कॉल से पूरे स्टाॅक में अंतर करें।
1.1। सबरूटीन्स
एक सबरूटीन स्थानीय चर रखने के लिए एक नए स्टैक स्तर का प्रतिनिधित्व करता है, और अंत तक पहुंचने के लिए अपने निर्देशों का एक एकल ट्रावेल। इस तरह से एक सबरूटीन पर विचार करें:
def subfoo(bar):
qux = 3
return qux * bar
जब आप इसे चलाते हैं, तो इसका मतलब है
- के लिए स्टैक स्थान आवंटित करें
bar
औरqux
- पहले कथन को पुनरावर्ती रूप से निष्पादित करें और अगले कथन पर जाएं
- एक बार एक बार
return
, कॉलिंग स्टैक के लिए इसके मूल्य को धक्का दें
- स्टैक को साफ़ करें (1.) और निर्देश सूचक (2.)
विशेष रूप से, 4. का अर्थ है कि एक उप-प्रजाति हमेशा एक ही राज्य में शुरू होती है। फ़ंक्शन के लिए विशेष सब कुछ पूरा होने पर खो जाता है। किसी फ़ंक्शन को फिर से शुरू नहीं किया जा सकता है, भले ही उसके बाद निर्देश हों return
।
root -\
: \- subfoo --\
:/--<---return --/
|
V
1.2। लगातार सबरूटीन्स के रूप में कोराटाइन्स
एक कोरआउट एक सबरूटीन की तरह है, लेकिन इसके राज्य को नष्ट किए बिना बाहर निकल सकता है । इस तरह से एक coroutine पर विचार करें:
def cofoo(bar):
qux = yield bar # yield marks a break point
return qux
जब आप इसे चलाते हैं, तो इसका मतलब है
- के लिए स्टैक स्थान आवंटित करें
bar
औरqux
- पहले कथन को पुनरावर्ती रूप से निष्पादित करें और अगले कथन पर जाएं
- एक बार एक बार
yield
, कॉलिंग स्टैक पर इसके मूल्य को धक्का दें, लेकिन स्टैक और निर्देश सूचक को संग्रहीत करें
- एक बार फोन करने पर
yield
, स्टैक और इंस्ट्रक्टर पॉइंटर को रिस्टोर करें और तर्कों को पुश करेंqux
- एक बार एक बार
return
, कॉलिंग स्टैक के लिए इसके मूल्य को धक्का दें
- स्टैक को साफ़ करें (1.) और निर्देश सूचक (2.)
2.1 और 2.2 के अतिरिक्त पर ध्यान दें - पूर्वनिर्धारित बिंदुओं पर एक कोरटाइन को निलंबित और फिर से शुरू किया जा सकता है। यह उसी तरह है जैसे किसी अन्य सबरूटीन को कॉल करने के दौरान एक सबरूटीन को कैसे निलंबित किया जाता है। अंतर यह है कि सक्रिय कोरटाइन अपने कॉलिंग स्टैक के लिए कड़ाई से बाध्य नहीं है। इसके बजाय, एक निलंबित कोरआउट एक अलग, पृथक स्टैक का हिस्सा है।
root -\
: \- cofoo --\
:/--<+--yield --/
| :
V :
इसका मतलब है कि निलंबित कोरआउट्स को स्वतंत्र रूप से संग्रहीत किया जा सकता है या ढेर के बीच स्थानांतरित किया जा सकता है। किसी भी कॉल स्टैक जिसमें कॉरटीन तक पहुंच है, उसे फिर से शुरू करने का निर्णय ले सकता है।
1.3। कॉल स्टैक को ट्रैवर्स करना
अब तक, हमारी कोरआउट केवल कॉल स्टैक के साथ नीचे जाती है yield
। एक सबरूटीन नीचे जा सकता है और ऊपर के साथ कॉल स्टैक return
और ()
। पूर्णता के लिए, कॉलआउट्स को कॉल स्टैक पर जाने के लिए एक तंत्र की भी आवश्यकता होती है। इस तरह से एक coroutine पर विचार करें:
def wrap():
yield 'before'
yield from cofoo()
yield 'after'
जब आप इसे चलाते हैं, तो इसका मतलब है कि यह अभी भी सबरूटीन की तरह स्टैक और इंस्ट्रक्शन पॉइंटर आवंटित करता है। जब यह सस्पेंड होता है, तब भी यह सबरूटीन स्टोर करने जैसा है।
हालाँकि, दोनोंyield from
करता है । यह स्टैक और निर्देश सूचक को निलंबित करता है और चलाता है । ध्यान दें कि पूरी तरह से खत्म होने तक निलंबित रहता है । जब भी निलंबित या कुछ भेजा जाता है, सीधे कॉलिंग स्टैक से जुड़ा होता है।wrap
cofoo
wrap
cofoo
cofoo
cofoo
1.4। सभी तरह से नीचे Coroutines
जैसा कि स्थापित किया गया है, yield from
एक दूसरे मध्यवर्ती के पार दो स्कोप को जोड़ने की अनुमति देता है। जब पुनरावर्ती रूप से लागू किया जाता है, तो इसका मतलब है कि स्टैक के शीर्ष को स्टैक के नीचे से जोड़ा जा सकता है ।
root -\
: \-> coro_a -yield-from-> coro_b --\
:/ <-+------------------------yield ---/
| :
:\ --+-- coro_a.send----------yield ---\
: coro_b <-/
ध्यान दें root
और coro_b
एक दूसरे के बारे में नहीं जानते। यह कॉलबैक की तुलना में कोरआउट को बहुत अधिक स्वच्छ बनाता है: अभी भी सबरूटीन की तरह 1: 1 के संबंध में निर्मित कोरटाइन। नियमित कॉल बिंदु तक कॉरुटिनेस अपने पूरे मौजूदा निष्पादन को रोकते हैं और फिर से शुरू करते हैं।
विशेष रूप से, root
फिर से शुरू करने के लिए कॉरटाइन्स की एक मनमानी संख्या हो सकती है। फिर भी, यह एक ही समय में एक से अधिक को फिर से शुरू नहीं कर सकता है। एक ही मूल के कोराटाइन समवर्ती होते हैं लेकिन समानांतर नहीं!
1.5। अजगर का async
औरawait
स्पष्टीकरण ने अभी तक स्पष्ट रूप से जनरेटर की शब्दावली yield
और yield from
शब्दावली का उपयोग किया है - अंतर्निहित कार्यक्षमता समान है। नया Python3.5 वाक्य रचना async
और await
स्पष्टता के लिए मुख्य रूप से मौजूद है।
def foo(): # subroutine?
return None
def foo(): # coroutine?
yield from foofoo() # generator? coroutine?
async def foo(): # coroutine!
await foofoo() # coroutine!
return None
async for
और async with
क्योंकि आप टूट जाएगा बयान की जरूरत है yield from/await
नंगे के साथ श्रृंखला for
और with
बयान।
2. एक साधारण घटना पाश की शारीरिक रचना
अपने आप में, एक coroutine को दूसरे coroutine को नियंत्रण देने की कोई अवधारणा नहीं है । यह केवल एक coroutine स्टैक के नीचे कॉल करने वाले को नियंत्रण दे सकता है। यह कॉलर तब किसी अन्य कॉरआउट में जा सकता है और इसे चला सकता है।
कई कोरआउटों का यह रूट नोड आमतौर पर एक ईवेंट लूप है : निलंबन पर, एक कोरआउट एक घटना उत्पन्न करता है, जिस पर वह फिर से शुरू करना चाहता है। बदले में, इवेंट लूप कुशलतापूर्वक इन घटनाओं के होने की प्रतीक्षा करने में सक्षम है। यह यह तय करने की अनुमति देता है कि कौन सा कोरटाइन आगे चल रहा है, या फिर से शुरू होने से पहले कैसे प्रतीक्षा करें।
इस तरह के डिजाइन का अर्थ है कि पूर्व-परिभाषित घटनाओं का एक सेट है जो लूप समझता है। await
जब तक कोई ईवेंट await
एड न हो, तब तक एक-दूसरे को कई कॉरआउट करते हैं । यह घटना आईएनजी नियंत्रण के साथ घटना लूप के साथ सीधे संवाद कर सकती है yield
।
loop -\
: \-> coroutine --await--> event --\
:/ <-+----------------------- yield --/
| :
| : # loop waits for event to happen
| :
:\ --+-- send(reply) -------- yield --\
: coroutine <--yield-- event <-/
कुंजी यह है कि कोरटाउइन निलंबन इवेंट लूप और घटनाओं को सीधे संवाद करने की अनुमति देता है। मध्यवर्ती कोरआउट स्टैक को किसी भी ज्ञान की आवश्यकता नहीं होती है कि कौन सा लूप इसे चला रहा है, और न ही ईवेंट कैसे काम करते हैं।
2.1.1। समय में घटनाएँ
समय पर पहुंचने के लिए सबसे सरल घटना है। यह थ्रेडेड कोड का एक मौलिक ब्लॉक है: sleep
एक शर्त के सच होने तक बार-बार एक धागा । हालांकि, एक नियमित sleep
ब्लॉक निष्पादन अपने आप होता है - हम चाहते हैं कि अन्य कोरटाइन अवरुद्ध न हों। इसके बजाय, हम इवेंट लूप को तब बताना चाहते हैं जब उसे वर्तमान कोरआउट स्टैक फिर से शुरू करना चाहिए।
2.1.2। एक घटना को परिभाषित करना
एक घटना केवल एक मूल्य है जिसे हम पहचान सकते हैं - यह एक Enum, एक प्रकार या अन्य पहचान के माध्यम से हो सकता है। हम इसे एक साधारण वर्ग के साथ परिभाषित कर सकते हैं जो हमारे लक्ष्य समय को संग्रहीत करता है। घटना की जानकारी संग्रहीत करने के अलावा , हम await
एक कक्षा को सीधे अनुमति दे सकते हैं।
class AsyncSleep:
"""Event to sleep until a point in time"""
def __init__(self, until: float):
self.until = until
# used whenever someone ``await``s an instance of this Event
def __await__(self):
# yield this Event to the loop
yield self
def __repr__(self):
return '%s(until=%.1f)' % (self.__class__.__name__, self.until)
यह वर्ग केवल घटना को संग्रहीत करता है - यह नहीं कहता है कि वास्तव में इसे कैसे संभालना है।
एकमात्र विशेष विशेषता __await__
यह है - यह वह है जिसके लिए await
कीवर्ड दिखता है। व्यावहारिक रूप से, यह एक पुनरावृत्ति है, लेकिन नियमित पुनरावृत्ति मशीनरी के लिए उपलब्ध नहीं है।
2.2.1। किसी घटना की प्रतीक्षा में
अब जब हमारे पास एक ईवेंट है, तो कोरटाइन इस पर कैसे प्रतिक्रिया करते हैं? हमें अपने ईवेंट के sleep
द्वारा समतुल्य व्यक्त करने में सक्षम होना चाहिए await
। यह देखने के लिए कि क्या चल रहा है, हम आधे समय के लिए दो बार प्रतीक्षा करते हैं:
import time
async def asleep(duration: float):
"""await that ``duration`` seconds pass"""
await AsyncSleep(time.time() + duration / 2)
await AsyncSleep(time.time() + duration / 2)
हम सीधे इस तात्कालिकता को चला सकते हैं और चला सकते हैं। जनरेटर के समान, इसका उपयोग coroutine.send
करने तक कोरटाइन चलाता है yield
।
coroutine = asleep(100)
while True:
print(coroutine.send(None))
time.sleep(0.1)
यह हमें दो AsyncSleep
घटनाएँ देता है और फिर StopIteration
जब कोरटाइन किया जाता है। ध्यान दें कि केवल देरी time.sleep
लूप से है! प्रत्येक AsyncSleep
केवल वर्तमान समय से ऑफसेट संग्रहीत करता है।
2.2.2। घटना + नींद
इस बिंदु पर, हमारे पास अपने निपटान में दो अलग-अलग तंत्र हैं:
AsyncSleep
ऐसी घटनाएँ जो एक कोरटीन के अंदर से उपज सकती हैं
time.sleep
यह कोरटाइन को प्रभावित किए बिना इंतजार कर सकता है
विशेष रूप से, ये दोनों ऑर्थोगोनल हैं: कोई भी दूसरे को प्रभावित या ट्रिगर नहीं करता है। परिणामस्वरूप, हम sleep
देरी की पूर्ति के लिए अपनी रणनीति के साथ आ सकते हैं AsyncSleep
।
2.3। एक भोली घटना लूप
यदि हमारे पास कई कॉरआउट हैं, तो प्रत्येक हमें बता सकता है कि वह कब जागना चाहता है। हम तब तक इंतजार कर सकते हैं जब तक कि उनमें से पहले को फिर से शुरू नहीं किया जाना चाहिए, फिर एक के बाद के लिए, और इसी तरह। विशेष रूप से, प्रत्येक बिंदु पर हम केवल इस बात की परवाह करते हैं कि कौन सा अगला है ।
यह एक सरल समय-निर्धारण के लिए बनाता है:
- उनके इच्छित समय के आधार पर क्राउटइन को क्रमबद्ध करें
- पहले उठो जो जगाना चाहता है
- इस समय तक प्रतीक्षा करें
- इस coroutine को चलाएं
- 1 से दोहराएं।
एक तुच्छ कार्यान्वयन को किसी उन्नत अवधारणा की आवश्यकता नहीं है। A list
आज तक कोरटाइन को क्रमबद्ध करने की अनुमति देता है। प्रतीक्षा एक नियमित है time.sleep
। कोरटाइन चलाना पहले की तरह ही काम करता है coroutine.send
।
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
# store wake-up-time and coroutines
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting:
# 2. pick the first coroutine that wants to wake up
until, coroutine = waiting.pop(0)
# 3. wait until this point in time
time.sleep(max(0.0, until - time.time()))
# 4. run this coroutine
try:
command = coroutine.send(None)
except StopIteration:
continue
# 1. sort coroutines by their desired suspension
if isinstance(command, AsyncSleep):
waiting.append((command.until, coroutine))
waiting.sort(key=lambda item: item[0])
बेशक, इसमें सुधार के लिए पर्याप्त जगह है। हम प्रतीक्षा कतार या घटनाओं के लिए प्रेषण तालिका के लिए एक ढेर का उपयोग कर सकते हैं। हम वापसी मूल्यों को भी प्राप्त कर सकते हैं StopIteration
और उन्हें कोरटाइन को सौंप सकते हैं। हालांकि, मौलिक सिद्धांत एक ही है।
2.4। सहकारी प्रतीक्षा
AsyncSleep
घटना और run
घटना पाश समय समाप्त हो गया घटनाओं की एक पूरी तरह से काम कर रहा कार्यान्वयन कर रहे हैं।
async def sleepy(identifier: str = "coroutine", count=5):
for i in range(count):
print(identifier, 'step', i + 1, 'at %.2f' % time.time())
await asleep(0.1)
run(*(sleepy("coroutine %d" % j) for j in range(5)))
यह सहक्रियात्मक रूप से पांचों कोरटाइनों के बीच स्विच करता है, प्रत्येक को 0.1 सेकंड के लिए निलंबित करता है। भले ही ईवेंट लूप समकालिक हो, फिर भी यह 2.5 सेकंड के बजाय 0.5 सेकंड में कार्य को निष्पादित करता है। प्रत्येक कोरटाइन राज्य रखता है और स्वतंत्र रूप से कार्य करता है।
3. I / O ईवेंट लूप
एक इवेंट लूप जो समर्थन करता sleep
है वह मतदान के लिए उपयुक्त है । हालाँकि, फ़ाइल हैंडल पर I / O की प्रतीक्षा अधिक कुशलता से की जा सकती है: ऑपरेटिंग सिस्टम I / O को लागू करता है और इस प्रकार जानता है कि कौन सा हैंडल तैयार है। आदर्श रूप से, एक ईवेंट लूप को एक स्पष्ट "आई / ओ" इवेंट के लिए तैयार का समर्थन करना चाहिए।
3.1। select
कॉल
पायथन में पहले से ही I / O हैंडल पढ़ने के लिए OS को क्वेरी करने के लिए एक इंटरफ़ेस है। जब पढ़ने या लिखने के लिए हैंडल के साथ कॉल किया जाता है, तो यह पढ़ने या लिखने के लिए तैयार हैंडल लौटाता है :
readable, writeable, _ = select.select(rlist, wlist, xlist, timeout)
उदाहरण के लिए, हम open
लिखने के लिए एक फ़ाइल बना सकते हैं और इसके तैयार होने की प्रतीक्षा कर सकते हैं :
write_target = open('/tmp/foo')
readable, writeable, _ = select.select([], [write_target], [])
एक बार रिटर्न का चयन करें, writeable
हमारी खुली फाइल शामिल है।
3.2। मूल I / O घटना
AsyncSleep
अनुरोध के समान , हमें I / O के लिए एक घटना को परिभाषित करने की आवश्यकता है। अंतर्निहित select
तर्क के साथ, घटना को एक पठनीय वस्तु का उल्लेख करना चाहिए - एक open
फ़ाइल कहो । इसके अलावा, हम स्टोर करते हैं कि कितना डेटा पढ़ना है।
class AsyncRead:
def __init__(self, file, amount=1):
self.file = file
self.amount = amount
self._buffer = ''
def __await__(self):
while len(self._buffer) < self.amount:
yield self
# we only get here if ``read`` should not block
self._buffer += self.file.read(1)
return self._buffer
def __repr__(self):
return '%s(file=%s, amount=%d, progress=%d)' % (
self.__class__.__name__, self.file, self.amount, len(self._buffer)
)
जैसा कि AsyncSleep
हम ज्यादातर अंतर्निहित सिस्टम कॉल के लिए आवश्यक डेटा संग्रहीत करते हैं। यह समय, __await__
कई बार शुरू होने में सक्षम है - जब तक कि हमारा वांछित amount
पढ़ा नहीं गया है। इसके अलावा, हम return
I / O परिणाम को फिर से शुरू करने के बजाय।
3.3। ई / ओ के साथ एक ईवेंट लूप को संवर्धित करना
हमारे ईवेंट लूप का आधार अभी भी run
पहले से परिभाषित है। सबसे पहले, हमें रीड रिक्वेस्ट को ट्रैक करना होगा। यह अब एक क्रमबद्ध शेड्यूल नहीं है, हम केवल कोरटाइन के लिए अनुरोध पढ़ते हैं।
# new
waiting_read = {} # type: Dict[file, coroutine]
चूंकि select.select
टाइमआउट पैरामीटर लिया जाता है, इसलिए हम इसका उपयोग कर सकते हैं time.sleep
।
# old
time.sleep(max(0.0, until - time.time()))
# new
readable, _, _ = select.select(list(reads), [], [])
यह हमें सभी पठनीय फाइलें देता है - यदि कोई हैं, तो हम संगत कॉरआउट चलाते हैं। यदि कोई नहीं है, तो हमने अपने वर्तमान कोरआउट को चलाने के लिए काफी लंबा इंतजार किया है।
# new - reschedule waiting coroutine, run readable coroutine
if readable:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read[readable[0]]
अंत में, हमें वास्तव में रीड रिक्वेस्ट के लिए सुनना होगा।
# new
if isinstance(command, AsyncSleep):
...
elif isinstance(command, AsyncRead):
...
3.4। इसे एक साथ रखना
ऊपर एक सरलीकरण था। अगर हम हमेशा पढ़ सकते हैं, तो हमें नींद से बाहर निकलने वाले तारों को नहीं करने के लिए कुछ स्विचिंग करने की आवश्यकता है। हमें पढ़ने के लिए कुछ नहीं होना चाहिए और न ही कुछ करना चाहिए। हालांकि, अंतिम परिणाम अभी भी 30 एलओसी में फिट बैठता है।
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
waiting_read = {} # type: Dict[file, coroutine]
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting or waiting_read:
# 2. wait until the next coroutine may run or read ...
try:
until, coroutine = waiting.pop(0)
except IndexError:
until, coroutine = float('inf'), None
readable, _, _ = select.select(list(waiting_read), [], [])
else:
readable, _, _ = select.select(list(waiting_read), [], [], max(0.0, until - time.time()))
# ... and select the appropriate one
if readable and time.time() < until:
if until and coroutine:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read.pop(readable[0])
# 3. run this coroutine
try:
command = coroutine.send(None)
except StopIteration:
continue
# 1. sort coroutines by their desired suspension ...
if isinstance(command, AsyncSleep):
waiting.append((command.until, coroutine))
waiting.sort(key=lambda item: item[0])
# ... or register reads
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
3.5। सहकारी I / ओ
AsyncSleep
, AsyncRead
और run
कार्यान्वयन अब पूरी तरह से नींद और / या पढ़ने के लिए कार्य कर रहे हैं। के रूप में ही sleepy
, हम एक सहायक को पढ़ने का परीक्षण परिभाषित कर सकते हैं:
async def ready(path, amount=1024*32):
print('read', path, 'at', '%d' % time.time())
with open(path, 'rb') as file:
result = return await AsyncRead(file, amount)
print('done', path, 'at', '%d' % time.time())
print('got', len(result), 'B')
run(sleepy('background', 5), ready('/dev/urandom'))
इसे चलाने पर, हम देख सकते हैं कि हमारा I / O वेटिंग कार्य के साथ इंटरलेस्ड है:
id background round 1
read /dev/urandom at 1530721148
id background round 2
id background round 3
id background round 4
id background round 5
done /dev/urandom at 1530721148
got 1024 B
4. गैर-अवरोधक I / O
मैं / फाइलों पर हे अवधारणा भर में हो जाता है, वहीं यह की तरह एक पुस्तकालय के लिए वास्तव में उपयुक्त नहीं है asyncio
: select
कॉल हमेशा फ़ाइलों के लिए रिटर्न , और दोनों open
और read
हो सकता है अनिश्चित काल के लिए ब्लॉक । यह ईवेंट लूप के सभी कोरआउट्स को अवरुद्ध करता है - जो खराब है। लाइब्रेरीज़ जैसे aiofiles
थ्रेड्स का उपयोग करते हैं और नकली गैर-अवरोधक I / O और फ़ाइल पर ईवेंट को सिंक्रोनाइज़ करते हैं।
हालांकि, सॉकेट्स गैर-अवरुद्ध I / O के लिए अनुमति देते हैं - और उनकी अंतर्निहित विलंबता इसे और अधिक महत्वपूर्ण बनाती है। जब एक घटना लूप में उपयोग किया जाता है, तो डेटा का इंतजार करना और पुन: प्रयास करना कुछ भी अवरुद्ध किए बिना लपेटा जा सकता है।
4.1। गैर-अवरोधक I / O घटना
हमारे समान AsyncRead
, हम सॉकेट्स के लिए एक सस्पेंड-एंड-रीड इवेंट को परिभाषित कर सकते हैं। एक फ़ाइल लेने के बजाय, हम एक सॉकेट लेते हैं - जो गैर-अवरुद्ध होना चाहिए। इसके अलावा, हमारे __await__
उपयोग के socket.recv
बजाय file.read
।
class AsyncRecv:
def __init__(self, connection, amount=1, read_buffer=1024):
assert not connection.getblocking(), 'connection must be non-blocking for async recv'
self.connection = connection
self.amount = amount
self.read_buffer = read_buffer
self._buffer = b''
def __await__(self):
while len(self._buffer) < self.amount:
try:
self._buffer += self.connection.recv(self.read_buffer)
except BlockingIOError:
yield self
return self._buffer
def __repr__(self):
return '%s(file=%s, amount=%d, progress=%d)' % (
self.__class__.__name__, self.connection, self.amount, len(self._buffer)
)
इसके विपरीत AsyncRead
, __await__
वास्तव में गैर-अवरुद्ध I / O करता है। जब डेटा उपलब्ध होता है, तो यह हमेशा पढ़ता है। जब कोई डेटा उपलब्ध नहीं होता है, तो यह हमेशा निलंबित रहता है। इसका मतलब है कि ईवेंट लूप केवल अवरुद्ध है जब हम उपयोगी कार्य करते हैं।
4.2। इवेंट लूप को अन-ब्लॉक करना
जहां तक इवेंट लूप का सवाल है, कुछ भी ज्यादा नहीं बदलता है। सुनने के लिए घटना अभी भी फ़ाइलों के लिए के रूप में ही है - एक फ़ाइल विवरणक द्वारा चिह्नित तैयार select
।
# old
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
# new
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
waiting_read[command.connection] = coroutine
इस बिंदु पर, यह स्पष्ट होना चाहिए कि AsyncRead
और AsyncRecv
उसी तरह की घटना है। हम आसानी से उन्हें एक विनिमेय I / O घटक के साथ एक घटना होने के लिए रिफ्लेक्टर कर सकते हैं । वास्तव में, ईवेंट लूप, कोरआउट और ईवेंट्स एक अनुसूचक, मनमाने ढंग से मध्यवर्ती कोड और वास्तविक I / O को अलग-अलग करते हैं।
4.3। गैर-अवरुद्ध I / O का बदसूरत पक्ष
सिद्धांत रूप में, क्या आप इस बिंदु पर क्या करना चाहिए के तर्क को दोहराने है read
एक के रूप में recv
के लिए AsyncRecv
। हालांकि, यह अब बहुत अधिक बदसूरत है - आपको कर्नेल के अंदर फ़ंक्शन ब्लॉक होने पर शुरुआती रिटर्न को संभालना होगा, लेकिन आपको नियंत्रण प्राप्त होगा। उदाहरण के लिए, किसी फ़ाइल को खोलना बनाम कनेक्शन खोलना बहुत लंबा है:
# file
file = open(path, 'rb')
# non-blocking socket
connection = socket.socket()
connection.setblocking(False)
# open without blocking - retry on failure
try:
connection.connect((url, port))
except BlockingIOError:
pass
लंबी कहानी छोटी, जो बनी हुई है वह अपवाद की हैंडलिंग की कुछ दर्जन लाइनें हैं। ईवेंट और इवेंट लूप पहले से ही इस बिंदु पर काम करते हैं।
id background round 1
read localhost:25000 at 1530783569
read /dev/urandom at 1530783569
done localhost:25000 at 1530783569 got 32768 B
id background round 2
id background round 3
id background round 4
done /dev/urandom at 1530783569 got 4096 B
id background round 5
परिशिष्ट
उदाहरण कोड github पर
BaseEventLoop
कार्यान्वित किया जाता है इसे देखें: github.com/python/cpython/blob/…