কথা বলা async/await
এবং asyncio
একই জিনিস নয়। প্রথমটি হল একটি মৌলিক, নিম্ন-স্তরের কনস্ট্রাক্ট (করাউটাইনস) যখন পরে এই নির্মাণগুলি ব্যবহার করে একটি লাইব্রেরি হয়। বিপরীতে, কোন একক চূড়ান্ত উত্তর নেই।
নীচে কীভাবে async/await
এবং asyncio
পছন্দসই লাইব্রেরিগুলি কাজ করে তার একটি সাধারণ বিবরণ দেওয়া হল । অর্থাৎ উপরে অন্য কৌশলগুলি থাকতে পারে (রয়েছে ...) তবে আপনি নিজে তৈরি না করলে এগুলি অনর্থক। পার্থক্যটি নগণ্য হওয়া উচিত যদি না আপনি ইতিমধ্যে এমন প্রশ্ন জিজ্ঞাসা না করার যথেষ্ট পরিমাণে জানতে চান।
1. বাদামের শেলের মধ্যে কার্টাইনগুলি বনাম সাবরুটাইনগুলি
ঠিক যেমন সাবরুটাইনস (ফাংশন, পদ্ধতি, ...), কর্টিনস (জেনারেটর, ...) কল স্ট্যাক এবং নির্দেশ নির্দেশকের একটি বিমূর্ততা: কোড টুকরোগুলি কার্যকর করার একটি স্ট্যাক রয়েছে এবং প্রতিটি নির্দিষ্ট নির্দেশে রয়েছে।
এর পার্থক্য def
বনাম async def
নিছক স্বচ্ছতার জন্য নয়। আসল পার্থক্য return
বনাম yield
। এটি থেকে, await
বা yield from
পৃথক কল থেকে পুরো স্ট্যাকের মধ্যে পার্থক্য নিন।
1.1। সাবরুটাইনস
একটি সাব্রুটিন স্থানীয় ভেরিয়েবলগুলি ধরে রাখতে একটি নতুন স্ট্যাক স্তর এবং তার শেষের দিকে পৌঁছানোর জন্য এর নির্দেশাবলীর একক ট্র্যাভারসাল উপস্থাপন করে। এই মত একটি subroutine বিবেচনা করুন:
def subfoo(bar):
qux = 3
return qux * bar
আপনি এটি চালানোর সময়, এর মানে হল
bar
এবং জন্য স্ট্যাক স্থান বরাদ্দqux
- পুনরাবৃত্তভাবে প্রথম বিবৃতিটি কার্যকর করুন এবং পরবর্তী বিবৃতিতে ঝাঁপুন
- একবারে একবার
return
, এর মানটি কলিং স্ট্যাকের দিকে ঠেলাও
- স্ট্যাকটি সাফ করুন (১) এবং নির্দেশিকা নির্দেশক (২)
উল্লেখযোগ্যভাবে, 4. এর অর্থ হল যে সাব্রোটিন সর্বদা একই অবস্থায় শুরু হয়। ফাংশনটির সাথে খোদাই করা সমস্ত কিছু শেষ হওয়ার পরে হারিয়ে যায়। পরে কোনও নির্দেশ থাকলেও কোনও ফাংশন পুনরায় শুরু করা যায় না return
।
root -\
: \- subfoo --\
:/--<---return --/
|
V
১.২ অবিচ্ছিন্ন subroutines হিসাবে Coroutines
একটি কর্টিন সাব্রোটিনের মতো তবে এটি তার রাজ্যটি বিনষ্ট না করে প্রস্থান করতে পারে । এই মত একটি কর্টিন বিবেচনা করুন:
def cofoo(bar):
qux = yield bar
return qux
আপনি এটি চালানোর সময়, এর মানে হল
bar
এবং জন্য স্ট্যাক স্থান বরাদ্দqux
- পুনরাবৃত্তভাবে প্রথম বিবৃতিটি কার্যকর করুন এবং পরবর্তী বিবৃতিতে ঝাঁপুন
- একবারে
yield
, তার মানটি কলিং স্ট্যাকের দিকে ঠেলাও তবে স্ট্যাক এবং নির্দেশ পয়েন্টারটি সঞ্চয় করুন
- একবার কল করার পরে
yield
, স্ট্যাক এবং নির্দেশ পয়েন্টারটি পুনরুদ্ধার করুন এবং এতে যুক্তি ধাক্কা দিনqux
- একবারে একবার
return
, এর মানটি কলিং স্ট্যাকের দিকে ঠেলাও
- স্ট্যাকটি সাফ করুন (১) এবং নির্দেশিকা নির্দেশক (২)
2.1 এবং 2.2 এর সংযোজনটি নোট করুন - একটি কর্টিন স্থগিত করা এবং পূর্বনির্ধারিত পয়েন্টগুলিতে পুনরায় শুরু করা যেতে পারে। এটি অন্য সাব্রুটিন কল করার সময় কীভাবে একটি সাব্রুটিন স্থগিত করা হয় তার অনুরূপ। পার্থক্যটি হল যে সক্রিয় কর্টিনটি তার কলিং স্ট্যাকের সাথে কঠোরভাবে আবদ্ধ নয়। পরিবর্তে, একটি স্থগিত কর্টিন একটি পৃথক পৃথক স্ট্যাকের অংশ।
root -\
: \- cofoo --\
:/--<+--yield --/
| :
V :
এর অর্থ হ'ল স্থগিত কর্টিনগুলি অবাধে সঞ্চয় করা বা স্ট্যাকের মধ্যে স্থানান্তরিত করা যেতে পারে। যে কোনও কল স্ট্যাকের কোনও কর্টিনে অ্যাক্সেস রয়েছে তা আবার চালু করার সিদ্ধান্ত নিতে পারে।
1.3। ট্র্যাকিং কল স্ট্যাক
এখনও অবধি, আমাদের কর্টিন কেবল কল স্ট্যাকের সাথে নেমে যায় yield
। একটি সাবরুটিন নিচে যেতে পারেন এবং আপ সহ কল স্ট্যাক return
এবং ()
। সম্পূর্ণতার জন্য, কর্টাইনগুলিতে কল স্ট্যাকের উপরে যাওয়ার জন্য একটি প্রক্রিয়াও প্রয়োজন need এই মত একটি কর্টিন বিবেচনা করুন:
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
পুনরায় শুরু করার জন্য নির্বিচারে সংখ্যক কর্টিন থাকতে পারে। তবুও, এটি একইসাথে একের বেশি পুনরায় আরম্ভ করতে পারে না। একই মূলের কর্টিনগুলি সমান্তরাল তবে সমান্তরাল নয়!
১.৫ পাইথনের async
এবংawait
ব্যাখ্যাটি এখনও অবধি জেনারেটরের শব্দ yield
এবং yield from
শব্দাবলী ব্যবহার করেছে - অন্তর্নিহিত কার্যকারিতা একই ality নতুন পাইথন 3.5 সিনট্যাক্স async
এবং await
মূলত স্পষ্টতার জন্য বিদ্যমান।
def foo():
return None
def foo():
yield from foofoo()
async def foo():
await foofoo()
return None
async for
এবং async with
কারণ আপনার বিরতি দেবে বিবৃতি প্রয়োজন হয় yield from/await
খালি সঙ্গে শৃঙ্খল for
এবং with
বিবৃতি।
২. একটি সাধারণ ইভেন্ট লুপের অ্যানাটমি
নিজেই, একটি কর্টিনের অন্য কোনও কর্টিনে নিয়ন্ত্রণ উত্পাদন করার কোনও ধারণা নেই । এটি কেবল কোনও করোটিন স্ট্যাকের নীচে কলারকে নিয়ন্ত্রণ দিতে পারে। এই কলারটি পরে অন্য কোনও কর্টিনে স্যুইচ করে এটি চালাতে পারে।
বেশ কয়েকটি কর্টিনের এই মূল নোডটি সাধারণত ইভেন্ট লুপ হয় : স্থগিতাদেশের পরে, কোনও কর্টিন একটি ইভেন্ট দেয় যা এটি পুনরায় শুরু করতে চায়। পরিবর্তে, ইভেন্ট লুপটি কার্যকরভাবে এই ইভেন্টগুলি হওয়ার জন্য অপেক্ষা করতে সক্ষম। এটি পরবর্তী কোন কর্টিন চালাবেন বা পুনরায় শুরু করার আগে কীভাবে অপেক্ষা করবেন তা সিদ্ধান্ত নিতে এটি আপনাকে অনুমতি দেয়।
এই জাতীয় নকশা বোঝায় যে পূর্বনির্ধারিত ইভেন্টগুলির একটি সেট রয়েছে যা লুপ বুঝতে পারে। await
একে অপরের বেশ কয়েকটি কর্টাইন থাকে, অবশেষে কোনও ইভেন্ট সম্পাদনা না হওয়া পর্যন্ত await
। এই ইভেন্টটি নিয়ন্ত্রণের মাধ্যমে ইভেন্ট লুপের সাথে সরাসরি যোগাযোগ করতে পারে yield
।
loop -\
: \-> coroutine --await--> event --\
:/ <-+----------------------- yield --/
| :
| :
| :
:\ --+-- send(reply) -------- yield --\
: coroutine <--yield-- event <-/
মূলটি হ'ল কর্টিন স্থগিতাদেশ ইভেন্ট লুপ এবং ইভেন্টগুলিকে সরাসরি যোগাযোগ করতে দেয়। মধ্যবর্তী কর্টিন স্ট্যাকের কোন লুপ এটি চালাচ্ছে বা কীভাবে ইভেন্টগুলি কাজ করে সে সম্পর্কে কোনও জ্ঞানের প্রয়োজন নেই ।
২.১.১। সময়ে ইভেন্ট
পরিচালনা করার সহজতম ইভেন্টটি সময়ে সময়ে পৌঁছে যাচ্ছে। এটি থ্রেডেড কোডেরও একটি মৌলিক ব্লক: sleep
শর্তটি সত্য না হওয়া পর্যন্ত একটি থ্রেড বারবার ব্যবহৃত হয়। যাইহোক, sleep
নিজেই নিয়মিত ব্লক সম্পাদন - আমরা চাই অন্য কর্টিনগুলি ব্লক করা উচিত নয়। পরিবর্তে, আমরা যখন বর্তমান কর্টিন স্ট্যাকটি আবার চালু করব তখন ইভেন্ট লুপটি বলতে চাই।
2.1.2। একটি ইভেন্ট সংজ্ঞা
একটি ইভেন্ট হ'ল একটি মূল্য যা আমরা সনাক্ত করতে পারি - এটি এনাম, কোনও প্রকার বা অন্য পরিচয়ের মাধ্যমে হোক। আমরা এটি একটি সাধারণ শ্রেণীর সাথে সংজ্ঞায়িত করতে পারি যা আমাদের টার্গেটের সময় সঞ্চয় করে। ইভেন্টের তথ্য সংরক্ষণ করার পাশাপাশি , আমরা await
সরাসরি কোনও শ্রেণিতে অনুমতি দিতে পারি ।
class AsyncSleep:
"""Event to sleep until a point in time"""
def __init__(self, until: float):
self.until = until
def __await__(self):
yield self
def __repr__(self):
return '%s(until=%.1f)' % (self.__class__.__name__, self.until)
এই শ্রেণিটি কেবল ইভেন্টটি সঞ্চয় করে - এটি কীভাবে এটি পরিচালনা করতে পারে তা বলে না।
একমাত্র বিশেষ বৈশিষ্ট্যটি হ'ল __await__
- await
কীওয়ার্ডটি এটির জন্য সন্ধান করে। ব্যবহারিকভাবে, এটি একটি পুনরাবৃত্তিকারী তবে নিয়মিত পুনরাবৃত্তি যন্ত্রের জন্য উপলব্ধ নয়।
2.2.1। একটি ইভেন্টের জন্য অপেক্ষা করছি
এখন যেহেতু আমাদের একটি ইভেন্ট রয়েছে, কর্টাইনগুলি এতে কীভাবে প্রতিক্রিয়া জানায়? আমরা সমতুল্য প্রকাশ করতে সক্ষম হওয়া উচিত sleep
দ্বারা await
আমাদের ইভেন্ট ing। কী চলছে তা আরও ভালভাবে দেখতে, আমরা আধবারের জন্য দু'বার অপেক্ষা করি:
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
এটি কর্টিনগুলিকে প্রভাবিত না করে অপেক্ষা করতে পারে
উল্লেখযোগ্যভাবে, এই দুটি orthogonal: একটিও অন্যকে প্রভাবিত করে না বা ট্রিগারও করে না। ফলস্বরূপ, আমরা sleep
একটি বিলম্ব মেটাতে আমাদের নিজস্ব কৌশল নিয়ে আসতে পারি AsyncSleep
।
2.3। একটি নিষ্পাপ ইভেন্ট লুপ
যদি আমাদের বেশ কয়েকটি কর্টিন থাকে তবে প্রত্যেকে এটি কখন জেগে উঠতে চায় তা আমাদের বলতে পারে। তারপরে আমরা অপেক্ষা করতে পারি যতক্ষণ না তাদের মধ্যে প্রথমটি আবার শুরু করা উচিত, তারপরের একজনের জন্য এবং আরও অনেক কিছু। উল্লেখযোগ্যভাবে, প্রতিটি বিন্দুতে আমরা কেবল যত্ন করি পরবর্তী কোনটি পরবর্তী ।
এটি একটি সরল সময়সূচী তৈরি করে:
- তাদের কাঙ্ক্ষিত জাগ্রত সময় অনুসারে কর্টাইনগুলি সাজান
- জেগে উঠতে চায় এমন প্রথমটি বেছে নিন
- এই সময় পর্যন্ত অপেক্ষা করুন
- এই কর্টিন চালান
- 1 থেকে পুনরাবৃত্তি।
তুচ্ছ বাস্তবায়নের জন্য কোনও উন্নত ধারণার প্রয়োজন হয় না। এ list
তারিখ অনুসারে কর্টাইনগুলিকে বাছাই করতে দেয়। অপেক্ষা নিয়মিত হয় time.sleep
। কারটিইনগুলি চালানো ঠিক আগের মতো কাজ করে coroutine.send
।
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting:
until, coroutine = waiting.pop(0)
time.sleep(max(0.0, until - time.time()))
try:
command = coroutine.send(None)
except StopIteration:
continue
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 সেকেন্ডের জন্য স্থগিত করে between ইভেন্ট লুপটি সিনক্রোনাস হলেও এটি এখনও 2.5 সেকেন্ডের পরিবর্তে 0.5 সেকেন্ডে কাজটি সম্পাদন করে। প্রতিটি কর্টিন রাষ্ট্র ধারণ করে এবং স্বাধীনভাবে কাজ করে।
3. আই / ও ইভেন্ট লুপ
সমর্থন করে sleep
এমন একটি ইভেন্ট লুপ ভোট দেওয়ার জন্য উপযুক্ত । যাইহোক, ফাইল হ্যান্ডলে I / O এর জন্য অপেক্ষা করা আরও দক্ষতার সাথে করা যেতে পারে: অপারেটিং সিস্টেম I / O প্রয়োগ করে এবং তাই হ্যান্ডলগুলি প্রস্তুত কিনা তা জানে। আদর্শভাবে, একটি ইভেন্ট লুপের একটি স্পষ্ট "I / O জন্য প্রস্তুত" ইভেন্ট সমর্থন করা উচিত।
৩.১০। select
কল
আই / ও হ্যান্ডলগুলি পঠনের জন্য পাইথনের ইতিমধ্যে ওএসকে জিজ্ঞাসা করার জন্য একটি ইন্টারফেস রয়েছে। হ্যান্ডলগুলি পড়তে বা লেখার জন্য ডাকা হলে, এটি পড়তে বা লিখতে প্রস্তুত হ্যান্ডলগুলি ফেরত দেয় :
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
অনুরোধ, আমরা ইনপুট / আউটপুট জন্য একটি ইভেন্ট সংজ্ঞায়িত করা প্রয়োজন। অন্তর্নিহিত 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
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। I / O পড়ার সাথে ইভেন্ট লুপকে উত্তেজিত করা
আমাদের ইভেন্ট লুপের ভিত্তি এখনও পূর্ব run
নির্ধারিত। প্রথমত, আমাদের পড়ার অনুরোধগুলি ট্র্যাক করতে হবে। এটি আর বাছাই করা সময়সূচী নয়, আমরা কেবল করটিইনগুলিতে পড়ার অনুরোধগুলি ম্যাপ করি।
waiting_read = {}
যেহেতু select.select
একটি সময়সীমা প্যারামিটার লাগে তাই আমরা এটি এর জায়গায় ব্যবহার করতে পারি time.sleep
।
time.sleep(max(0.0, until - time.time()))
readable, _, _ = select.select(list(reads), [], [])
এটি আমাদের সমস্ত পঠনযোগ্য ফাইল দেয় - যদি কোনও থাকে তবে আমরা সংশ্লিষ্ট Coroutine চালাই। যদি কিছুই না থাকে তবে আমরা আমাদের বর্তমান কর্টিনটি চালানোর জন্য যথেষ্ট অপেক্ষা করেছি।
if readable:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read[readable[0]]
শেষ অবধি, আমাদের পড়ার অনুরোধগুলি শুনতে হবে।
if isinstance(command, AsyncSleep):
...
elif isinstance(command, AsyncRead):
...
3.4। একসাথে রেখে
উপরেরটি কিছুটা সরলীকরণ ছিল। আমরা যদি সবসময় পড়তে পারি তবে ঘুমন্ত কর্টিনগুলিকে না খেয়ে থাকতে না করার জন্য আমাদের কিছু পরিবর্তন করতে হবে। আমাদের পড়ার মতো কিছুই নেই বা অপেক্ষা করার মতো কিছুই নেই handle যাইহোক, শেষ ফলাফলটি এখনও 30 এলওসি তে ফিট করে।
def run(*coroutines):
"""Cooperatively run all ``coroutines`` until completion"""
waiting_read = {}
waiting = [(0, coroutine) for coroutine in coroutines]
while waiting or waiting_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()))
if readable and time.time() < until:
if until and coroutine:
waiting.append((until, coroutine))
waiting.sort()
coroutine = waiting_read.pop(readable[0])
try:
command = coroutine.send(None)
except StopIteration:
continue
if isinstance(command, AsyncSleep):
waiting.append((command.until, coroutine))
waiting.sort(key=lambda item: item[0])
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
৩.৫ সমবায় আই / ও
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 = await AsyncRead(file, amount)
print('done', path, 'at', '%d' % time.time())
print('got', len(result), 'B')
run(sleepy('background', 5), ready('/dev/urandom'))
এটি চালিয়ে গেলে, আমরা দেখতে পাচ্ছি যে আমাদের আই / ও অপেক্ষার কাজটির সাথে অন্তর্নিহিত:
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
।
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRead):
waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
waiting_read[command.connection] = coroutine
এই মুহুর্তে, এটি স্পষ্ট হওয়া উচিত AsyncRead
এবং AsyncRecv
এটি একই ধরণের ইভেন্ট। আমরা সহজেই তাদেরকে বিনিময়যোগ্য I / O উপাদানযুক্ত একটি ইভেন্ট হিসাবে রিফ্যাক্টর করতে পারি। ফলস্বরূপ, ইভেন্ট লুপ, কর্টিনস এবং ইভেন্টগুলি পরিষ্কারভাবে একটি শিডিয়ুলার, স্বেচ্ছাসেবী ইন্টারমিডিয়েট কোড এবং প্রকৃত আই / ও আলাদা করে ।
4.3। অ-অবরুদ্ধকরণের I / O এর কুরুচিপূর্ণ দিক
বস্তুত, আপনি কি এই সময়ে করণীয় যুক্তি প্রতিলিপি হয় read
হিসেবে recv
জন্য AsyncRecv
। যাইহোক, এটি এখন আরও কুরুচিপূর্ণ - কার্নেলের ভিতরে ফাংশনগুলি ব্লক হয়ে গেলে আপনাকে প্রাথমিক রিটার্নগুলি পরিচালনা করতে হবে তবে আপনি নিয়ন্ত্রণ পেতে পারেন। উদাহরণস্বরূপ, একটি ফাইল খোলার তুলনায় একটি সংযোগ খোলার কাজটি অনেক বেশি দীর্ঘ:
file = open(path, 'rb')
connection = socket.socket()
connection.setblocking(False)
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
সংযোজন
গিথুবে কোড উদাহরণ
BaseEventLoop
কীভাবে প্রয়োগ করা হয়েছে তা দেখুন: github.com/python/cpython/blob/…