আসিনসিও আসলে কীভাবে কাজ করে?


140

এই প্রশ্নটি আমার অন্য প্রশ্ন দ্বারা অনুপ্রাণিত: সিডিএফ-এ কীভাবে অপেক্ষা করা যায়?

ওয়েবে প্রচুর নিবন্ধ এবং ব্লগ পোস্ট রয়েছে asyncio, তবে সেগুলি সমস্ত খুব পৃষ্ঠের। asyncioআসলে কীভাবে বাস্তবায়িত হয় এবং কী কারণে আই / ও অ্যাসিক্রোনাস হয় সে সম্পর্কে আমি কোনও তথ্য পাইনি । আমি উত্স কোডটি পড়ার চেষ্টা করছিলাম, তবে এটি সর্বোচ্চ গ্রেড সি কোডের হাজার হাজার লাইন নয়, যার অনেকগুলি সহায়ক বস্তুগুলির সাথে সম্পর্কিত, তবে সবচেয়ে গুরুত্বপূর্ণভাবে, পাইথন বাক্য গঠন এবং এটি কোন সি কোডটির সাথে অনুবাদ করবে এটি খুব কঠিন is মধ্যে।

এসাইকনিওর নিজস্ব ডকুমেন্টেশনগুলি আরও কম সহায়ক। এটি কীভাবে কাজ করে সে সম্পর্কে কোনও তথ্য নেই, এটি কীভাবে ব্যবহার করা যায় সে সম্পর্কে কেবল কয়েকটি গাইডলাইন রয়েছে যা কখনও কখনও বিভ্রান্তিকর / খুব খারাপভাবে লেখা থাকে।

আমি গো এর কর্টিনগুলি প্রয়োগের সাথে পরিচিত এবং আমি আশা করি যে পাইথনও একই কাজ করেছিল। যদি এটি হয় তবে উপরের লিঙ্কিত পোস্টে আমি যে কোডটি নিয়ে এসেছি সেগুলি কাজ করবে। যেহেতু এটি হয়নি, এখন আমি কেন এটি চেষ্টা করার চেষ্টা করছি। আমার এখন পর্যন্ত সর্বোত্তম অনুমানটি নিম্নরূপ: দয়া করে যেখানে আমি ভুল করছি আমাকে সংশোধন করুন:

  1. ফর্মের প্রক্রিয়া সংজ্ঞাগুলি async def foo(): ...আসলে শ্রেণীর উত্তরাধিকারের পদ্ধতি হিসাবে ব্যাখ্যা করা হয় coroutine
  2. সম্ভবত, বিবৃতি async defদ্বারা প্রকৃতপক্ষে একাধিক পদ্ধতিতে বিভক্ত করা awaitহয়েছে, যেখানে অবজেক্টটি, যার উপর এই পদ্ধতিগুলি বলা হয় এটি কার্যকর করার মাধ্যমে এটি এখনও পর্যন্ত যে অগ্রগতি করেছিল তা ট্র্যাক রাখতে সক্ষম হয়।
  3. যদি উপরেরটি সত্য হয়, তবে, প্রয়োজনীয়ভাবে, কোনও করউটিন কার্যকর করে কিছু গ্লোবাল ম্যানেজার (লুপ?) দ্বারা কর্টিন অবজেক্টের কল করার পদ্ধতিগুলিতে ফোটা যায়।
  4. পাইথন (কেবলমাত্র?) কোড দ্বারা আই / ও অপারেশনগুলি সঞ্চালিত হয় এবং বিশ্বব্যাপী পরিচালক একরকম (কীভাবে?) সচেতন এবং বর্তমান নির্বাহী পদ্ধতি ত্যাগের নিয়ন্ত্রণের পরে কার্যকর করার জন্য মুলতুবি থাকা কাউউটিন পদ্ধতির একটি বেছে নিতে সক্ষম হন ( awaitবিবৃতিতে আঘাত করুন )।

অন্য কথায়, এখানে asyncioআরও কিছু বোঝার মতো কিছু বাক্য গঠনের "ডিজুগারিং" করার আমার প্রচেষ্টা :

async def coro(name):
    print('before', name)
    await asyncio.sleep()
    print('after', name)

asyncio.gather(coro('first'), coro('second'))

# translated from async def coro(name)
class Coro(coroutine):
    def before(self, name):
        print('before', name)

    def after(self, name):
        print('after', name)

    def __init__(self, name):
        self.name = name
        self.parts = self.before, self.after
        self.pos = 0

    def __call__():
        self.parts[self.pos](self.name)
        self.pos += 1

    def done(self):
        return self.pos == len(self.parts)


# translated from asyncio.gather()
class AsyncIOManager:

    def gather(*coros):
        while not every(c.done() for c in coros):
            coro = random.choice(coros)
            coro()

আমার অনুমানটি যদি সঠিক প্রমাণিত হয়: তবে আমার একটি সমস্যা আছে। এই দৃশ্যে আসলে আমি / ও কীভাবে ঘটে? আলাদা সুত্রে? পুরো দোভাষীটি স্থগিত হয়ে যায় এবং আমি / ও দোভাষী এর বাইরেও ঘটে? আই / ও দ্বারা ঠিক কী বোঝানো হয়েছে? যদি আমার অজগর পদ্ধতিটি সি open()প্রক্রিয়া বলে, এবং এটি কার্নেলের কাছে বাধা প্রেরণ করে, তার থেকে নিয়ন্ত্রণ ত্যাগ করে, পাইথন ইন্টারপ্রেটার কীভাবে এটি জানতে পারে এবং অন্য কোনও কোড চালিয়ে যেতে সক্ষম হয়, যখন কার্নেল কোডটি আসল I / O এবং অবধি না করে এটি পাইথন পদ্ধতিটি জাগায় যা মূলত বাধা প্রেরণ করেছিল? নীতিগতভাবে পাইথন দোভাষী কীভাবে এই ঘটনার বিষয়ে সচেতন হতে পারেন?


4
বেশিরভাগ যুক্তিই ইভেন্ট লুপ প্রয়োগের দ্বারা পরিচালিত হয়। সিপিথন BaseEventLoopকীভাবে প্রয়োগ করা হয়েছে তা দেখুন: github.com/python/cpython/blob/…
ব্লেন্ডার

@ ব্লেন্ডার ঠিক আছে, আমি মনে করি অবশেষে আমি যা চেয়েছিলাম তা খুঁজে পেয়েছি তবে কোডটি যেভাবে লেখা হয়েছিল তার কারণ এখন আমি বুঝতে পারি না। কেন _run_once, এই সম্পূর্ণ মডিউলটিতে "প্রাইভেট" তৈরির কার্যকারী কোনটি আসলে? বাস্তবায়নটি ভয়াবহ, তবে এটি কোনও সমস্যা কম। ইভেন্ট লুপে আপনি কল করতে চান এমন একমাত্র ফাংশনটি কেন "আমাকে কল করবেন না" হিসাবে চিহ্নিত করা হয়েছে?
wvxvw

এটি মেলিং তালিকার জন্য একটি প্রশ্ন। কোন ক্ষেত্রে ব্যবহারের ক্ষেত্রে _run_onceপ্রথমে আপনাকে স্পর্শ করতে হবে ?
ব্লেন্ডার

8
যদিও এটি আসলেই আমার প্রশ্নের উত্তর দেয় না। আপনি কীভাবে কোনও কার্যকর সমস্যার সমাধান করবেন _run_once? asyncioজটিল এবং এর ত্রুটি রয়েছে, তবে দয়া করে আলোচনাটিকে সভ্য রাখুন। আপনি নিজেরাই বুঝতে পারছেন না এমন কোডের পিছনে বিকাশকারীদের বদনাম করবেন না।
ব্লেন্ডার

4
@ ইউজার 7719১১৫ যদি আপনি বিশ্বাস করেন যে আমি এমন কিছু নেই যা আমি আবৃত করি নি, আপনি আমার উত্তর যুক্ত করতে বা মন্তব্য করতে স্বাগত।
ভর্তি

উত্তর:


222

অ্যাসিনসিও কীভাবে কাজ করে?

এই প্রশ্নের উত্তর দেওয়ার আগে আমাদের কয়েকটি বেস শর্তাদি বুঝতে হবে, আপনি যদি ইতিমধ্যে সেগুলির কোনও জানেন তবে এগুলি এড়িয়ে যান।

জেনারেটর

জেনারেটর এমন বস্তু যা আমাদের অজগর ফাংশনটি স্থগিত করার অনুমতি দেয়। ব্যবহারকারী সংকলিত জেনারেটর শব্দ ব্যবহার করে বাস্তবায়ন করা হয় yield। মূলশব্দযুক্ত একটি সাধারণ ক্রিয়াকলাপ তৈরি করে yieldআমরা সেই ফাংশনটিকে একটি জেনারেটরে পরিণত করি:

>>> def test():
...     yield 1
...     yield 2
...
>>> gen = test()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

যেমন আপনি দেখতে পাচ্ছেন, next()জেনারেটরে কল করার ফলে দোভাষীকে পরীক্ষার ফ্রেম লোড করা যায় এবং yieldএডিট মানটি ফিরে আসে । next()আবার কল করা, ইন্টারপ্রেটার স্ট্যাকের মধ্যে ফ্রেমটি আবার লোড করার কারণ দিন, এবং yieldঅন্য কোনও মান নির্ধারণ করা চালিয়ে যান ।

তৃতীয় বার next()বলা হয়, আমাদের জেনারেটর সমাপ্ত হয়েছিল, এবং StopIterationনিক্ষেপ করা হয়েছিল।

একটি জেনারেটরের সাথে যোগাযোগ করা

: জেনারেটর একটি কম-পরিচিত বৈশিষ্ট্য যে, আসলে তুমি তাদের দুটি পদ্ধতি ব্যবহার করে যোগাযোগ করতে পারেন হয় send()এবং throw()

>>> def test():
...     val = yield 1
...     print(val)
...     yield 2
...     yield 3
...
>>> gen = test()
>>> next(gen)
1
>>> gen.send("abc")
abc
2
>>> gen.throw(Exception())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
Exception

কল করার পরে gen.send(), মানটি yieldকীওয়ার্ড থেকে ফেরত মান হিসাবে পাস করা হয় ।

gen.throw()অন্যদিকে, জেনারেটরের ভিতরে ব্যতিক্রম ছোঁড়ার অনুমতি দেয়, একই স্থানে উত্থাপিত ব্যতিক্রমকে yieldডাকা হত।

জেনারেটর থেকে মান ফেরত

একটি জেনারেটর থেকে একটি মান ফেরত, ফলাফল StopIterationব্যতিক্রম ভিতরে স্থাপন করা হয় । আমরা পরে ব্যতিক্রম থেকে মানটি পুনরুদ্ধার করতে এবং এটি আমাদের প্রয়োজন হিসাবে ব্যবহার করতে পারি।

>>> def test():
...     yield 1
...     return "abc"
...
>>> gen = test()
>>> next(gen)
1
>>> try:
...     next(gen)
... except StopIteration as exc:
...     print(exc.value)
...
abc

দেখুন, একটি নতুন কীওয়ার্ড: yield from

পাইথন 3.4 একটা নতুন শব্দ যোগে সাথে আসা: yield from। কি যে শব্দ আমাদের কি করার অনুমতি দেয়, কোন পাস হয় next(), send()এবং throw()একটি ভেতরের-পূর্বের নেস্টেড জেনারেটরের মধ্যে। যদি অভ্যন্তরীণ জেনারেটর কোনও মান দেয়, তবে এটির ফেরতের মানও yield from:

>>> def inner():
...     inner_result = yield 2
...     print('inner', inner_result)
...     return 3
...
>>> def outer():
...     yield 1
...     val = yield from inner()
...     print('outer', val)
...     yield 4
...
>>> gen = outer()
>>> next(gen)
1
>>> next(gen) # Goes inside inner() automatically
2
>>> gen.send("abc")
inner abc
outer 3
4

আমি এই বিষয়ে আরও বিস্তৃত করতে একটি নিবন্ধ লিখেছি ।

সবগুলোকে একত্রে রাখ

yield fromপাইথন ৩.৪- এ নতুন কীওয়ার্ডটি প্রবর্তন করার পরে , আমরা এখন জেনারেটরের অভ্যন্তরে জেনারেটর তৈরি করতে সক্ষম হয়েছি যা কেবল একটি টানেলের মতো, অভ্যন্তরীণ-বহিরাগত থেকে বহিরাগত জেনারেটরের কাছে ডেটা পিছনে পিছনে ফেলে দেয়। এটি জেনারেটর - করোটাইনগুলির জন্য একটি নতুন অর্থ তৈরি করেছে ।

Coroutines হ'ল ফাংশন যা চালানো চলাকালীন থামানো এবং পুনরায় চালু করা যেতে পারে। পাইথনে, তারা async defকীওয়ার্ড ব্যবহার করে সংজ্ঞায়িত করা হয় । অনেক জেনারেটর মতো, তারা তাদের নিজস্ব ফর্ম ব্যবহার yield fromযা await। পাইথন ৩.৩-এ প্রবর্তিত হওয়ার আগে asyncএবং এর আগে await, আমরা জেনারেটর তৈরি হয়েছিল ঠিক ঠিক তেমনভাবে কর্টিন তৈরি করেছি ( yield fromপরিবর্তে await))

async def inner():
    return 1

async def outer():
    await inner()

প্রতি পুনরুক্তিকারীর বা জেনারেটরের বাস্তবায়ন যে ভালো লেগেছে __iter__()পদ্ধতি, coroutines বাস্তবায়ন __await__()তাদেরকে প্রত্যেক সময় ধরে চলতে থাকার জন্য অনুমতি দেয় await coroবলা হয়।

পাইথন ডক্সের ভিতরে একটি দুর্দান্ত ক্রম ডায়াগ্রাম রয়েছে যা আপনার চেক করা উচিত।

অ্যাসিনসিওতে, কর্টিন ফাংশনগুলি বাদ দিয়ে আমাদের কাছে দুটি গুরুত্বপূর্ণ অবজেক্ট রয়েছে: টাস্ক এবং ফিউচার

ফিউচারস

ফিউচার __await__()হ'ল এমন বস্তু যা পদ্ধতি প্রয়োগ করেছে এবং তাদের কাজ একটি নির্দিষ্ট অবস্থা এবং ফলাফল রাখা। রাজ্য নিম্নলিখিতগুলির মধ্যে একটি হতে পারে:

  1. পেন্ডিং - ভবিষ্যতের কোনও ফলাফল বা ব্যতিক্রম সেট নেই।
  2. বাতিল - ভবিষ্যত ব্যবহার করে বাতিল করা হয়েছে fut.cancel()
  3. সমাপ্ত - ভবিষ্যতটি ব্যবহারের ফলাফল fut.set_result()দ্বারা বা কোনও ব্যতিক্রম সেট দ্বারা ব্যবহার করে সেট হয় eitherfut.set_exception()

ফলাফল, আপনি যেমন অনুমান করেছেন ঠিক তেমনই পাইথন অবজেক্ট হতে পারে, তা ফেরত দেওয়া হবে, বা ব্যতিক্রম যা উত্থাপিত হতে পারে।

অবজেক্টগুলির আরেকটি গুরুত্বপূর্ণ বৈশিষ্ট্য futureহ'ল এগুলির মধ্যে একটি পদ্ধতি রয়েছে add_done_callback()। এই পদ্ধতিটি কার্য সম্পাদন করার সাথে সাথে ফাংশনগুলিকে কল করার অনুমতি দেয় - এটি কোনও ব্যতিক্রম উত্থাপন করেছে বা শেষ হয়েছে কিনা finished

কাজ

টাস্ক অবজেক্টগুলি হ'ল বিশেষ ফিউচার, যা কর্টিনগুলির চারপাশে মোড়ানো থাকে এবং অভ্যন্তরীণ সর্বাধিক এবং বহিরাগত সর্বাধিক কর্টিনগুলির সাথে যোগাযোগ করে। প্রতিটি বার awaitভবিষ্যতে কোনও ভবিষ্যত, ভবিষ্যতের সমস্ত দিকটি টাস্কে ফিরে যায় (ঠিক তেমনই yield from), এবং কার্যটি এটি গ্রহণ করে।

পরবর্তী, টাস্ক নিজেকে ভবিষ্যতের সাথে আবদ্ধ করে। এটি add_done_callback()ভবিষ্যতের দিকে আহ্বান জানিয়ে তা করে। এখন থেকে, যদি ভবিষ্যতে কখনও হয়ে যায়, হয় হয় বাতিল হয়ে, একটি ব্যতিক্রম পাস করেছে বা ফলস্বরূপ পাইথন অবজেক্টটি পাস করার পরে, কার্যটির কলব্যাক ডাকা হবে এবং এটি আবার অস্তিত্বের দিকে ফিরে আসবে।

অ্যাসিনসিও

আমাদের চূড়ান্ত জ্বলন্ত প্রশ্নের উত্তর দিতে হবে - আইও কীভাবে প্রয়োগ করা হয়?

অ্যাসিনসিওর ভিতরে গভীর, আমাদের একটি ইভেন্ট লুপ রয়েছে। কাজের একটি ইভেন্ট লুপ। ইভেন্ট লুপের কাজ হ'ল প্রতিটি সময় কাজগুলি প্রস্তুত হওয়ার জন্য কল করা এবং সেই সমস্ত প্রচেষ্টাকে একটি একক ওয়ার্কিং মেশিনে সমন্বিত করা।

ইভেন্ট লুপের আইও অংশটি একটি একক ক্রিয়াল ফাংশন হিসাবে নির্মিত যা ডাকা হয় select। নির্বাচন হ'ল একটি ব্লকিং ফাংশন, যা নীচে অপারেটিং সিস্টেম দ্বারা প্রয়োগ করা হয়, যা আগত বা বহির্গামী ডেটার জন্য সকেটে অপেক্ষা করতে দেয়। ডেটা পাওয়ার পরে এটি জেগে ওঠে এবং যে সকেটগুলি ডেটা পেয়েছিল, বা সকেটগুলি লেখার জন্য প্রস্তুত রয়েছে তা ফেরত দেয়।

আপনি যখন অ্যাসিনসিওর মাধ্যমে কোনও সকেটের মাধ্যমে ডেটা গ্রহণ বা প্রেরণের চেষ্টা করেন, নীচে আসলে যা ঘটে তা হ'ল সকেটটি প্রথমে পরীক্ষা করা হয় যা এর সাথে সাথে এমন কোনও ডেটা রয়েছে যা তাৎক্ষণিকভাবে পড়া বা প্রেরণ করা যায়। তাহলে তার .send()বাফার পূর্ণ হয়, অথবা .recv()বাফার খালি, সকেট নিবন্ধিত হয় selectফাংশন (কেবল, তালিকা এক এটি যোগ করে rlistজন্য recvএবং wlistজন্য send) এবং উপযুক্ত ফাংশন awaitSA নব নির্মিত futureবস্তু, যে সকেট বাঁধা।

সমস্ত উপলভ্য কার্য যখন ফিউচারের জন্য অপেক্ষা করে তখন ইভেন্ট লুপ কল করে selectঅপেক্ষা করে। যখন কোনও সকেটের একটি ইনকামিং ডেটা থাকে, বা এর sendবাফার শুকিয়ে যায়, তখন অ্যাসিনসিও সেই সকেটের সাথে আবদ্ধ ভবিষ্যতের বস্তুটি পরীক্ষা করে সেটাকে সেট করে দেয়।

এখন সব ম্যাজিক হয়। ভবিষ্যতের কাজটি সেট হয়ে গেছে, সেই কাজটি যা পুনরুত্থানের সাথে আগে নিজেকে যুক্ত করেছিল add_done_callback()এবং .send()কর্টিনকে কল করে যা অভ্যন্তরীণ সর্বাধিক কর্টিনকে পুনরায় শুরু করে ( awaitচেইনের কারণে ) এবং আপনি এটি একটি কাছের বাফার থেকে নতুন প্রাপ্ত ডেটা পড়েন ছিটিয়ে ছিল।

ক্ষেত্রে আবার শৃঙ্খলা recv():

  1. select.select অপেক্ষা।
  2. ডেটা সহ একটি প্রস্তুত সকেট, ফেরত দেওয়া হয়।
  3. সকেট থেকে ডেটা একটি বাফারে সরানো হয়।
  4. future.set_result() বলা হয়.
  5. নিজের সাথে যুক্ত টাস্ক add_done_callback()এখন জেগে উঠেছে।
  6. .send()টাস্কটি কর্টিনকে কল করে যা পুরো পথটি অভ্যন্তরীণ সর্বাধিক কর্টিনে প্রবেশ করে এবং জেগে ওঠে।
  7. বাফার থেকে ডেটা পড়ছে এবং আমাদের নম্র ব্যবহারকারীর কাছে ফিরে এসেছে।

সংক্ষেপে, অ্যাসিনসিও জেনারেটর সক্ষমতা ব্যবহার করে, যা বিরাম দেওয়া এবং পুনরায় কাজ ফাংশনগুলির অনুমতি দেয়। এটি yield fromএমন ক্ষমতা ব্যবহার করে যা অভ্যন্তরীণ-সর্বাধিক জেনারেটর থেকে বহিরাগতের মধ্যে পিছনে তথ্য প্রেরণ করতে দেয়। আইওটি সম্পন্ন হওয়ার অপেক্ষায় থাকা (ওএস selectফাংশনটি ব্যবহার করে ) ফাংশন সম্পাদন বন্ধ করার জন্য এটি সমস্ত ব্যবহার করে ।

এবং সর্বোত্তম? যখন একটি ফাংশন বিরতি দেওয়া হয়েছে, অন্যটি চলতে এবং সূক্ষ্ম ফ্যাব্রিকের সাথে ইন্টারলিভ করতে পারে, এটি অ্যাসিনসিও।


13
যদি আরও কোনও ব্যাখ্যা প্রয়োজন হয় তবে মন্তব্য করতে দ্বিধা করবেন না। বিটিডব্লিউ, আমি পুরোপুরি নিশ্চিত নই যে এটি আমার কোনও ব্লগ নিবন্ধ বা স্ট্যাকওভারফ্লোতে উত্তর হিসাবে লেখা উচিত ছিল। প্রশ্নের উত্তর একটি দীর্ঘ এক।
ভরলে

4
একটি অ্যাসিঙ্ক্রোনাস সকেটে, ডেটা প্রেরণ বা গ্রহণের চেষ্টা করে প্রথমে ওএস বাফারটি পরীক্ষা করে। আপনি যদি গ্রহণের চেষ্টা করছেন এবং বাফারে কোনও ডেটা নেই, অন্তর্নিহিত প্রাপ্ত ফাংশনটি একটি ত্রুটি মান প্রদান করবে যা পাইথনের ব্যতিক্রম হিসাবে প্রচার করবে। প্রেরণ এবং একটি সম্পূর্ণ বাফার একই। ব্যতিক্রমটি উত্থাপিত হলে, পাইথন পরিবর্তে সেই সকেটগুলি নির্বাচন ফাংশনে প্রেরণ করে যা প্রক্রিয়াটি স্থগিত করে। তবে এটি কীভাবে অ্যাসিনসিওর কাজ করে তা নয়, এটি কীভাবে নির্বাচন করুন এবং সকেটগুলিও কাজ করে যা অত্যন্ত ওএস নির্দিষ্টভাবে নির্দিষ্ট।
ভেরাল

4
@ user8371915 সর্বদা সহায়তা করার জন্য এখানে :-) মনে রাখবেন যে অ্যাসিনসিও বুঝতে আপনাকে অবশ্যই জেনারেটর, জেনারেটর যোগাযোগ এবং কীভাবে yield fromকাজ করে তা অবশ্যই জানতে হবে । তবে আমি শীর্ষে নোট করে দিয়েছিলাম যে পাঠক এটির সম্পর্কে ইতিমধ্যে জানে যদি এটি ছেড়ে দেওয়া যায় :-) আপনি বিশ্বাস করেন যে আমার কিছু যুক্ত করা উচিত?
ভরলে

4
সামনে কিছু Asyncio হিসাবে তারা শুধু ভাষা আসলে নিজে করে হয় অধ্যায়, সম্ভবত সবচেয়ে জটিল। selectপাশাপাশি যোগ্যতা অর্জন করতে পারেন, যেহেতু এটি কিভাবে অ ব্লক ইনপুট / আউটপুট সিস্টেম OS এ কাজ কল। প্রকৃত asyncioনির্মাণ এবং ইভেন্ট লুপ এই জিনিসগুলি থেকে তৈরি অ্যাপ্লিকেশন স্তরের কোড।
মিস্টারমিয়াগি

4
এই পোস্টে পাইথনে অ্যাসিঙ্ক্রোনাস I / O এর মেরুদণ্ডের তথ্য রয়েছে। এই ধরনের একটি ব্যাখ্যা জন্য ধন্যবাদ।
mjkim

91

কথা বলা 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

আপনি এটি চালানোর সময়, এর মানে হল

  1. barএবং জন্য স্ট্যাক স্থান বরাদ্দqux
  2. পুনরাবৃত্তভাবে প্রথম বিবৃতিটি কার্যকর করুন এবং পরবর্তী বিবৃতিতে ঝাঁপুন
  3. একবারে একবার return, এর মানটি কলিং স্ট্যাকের দিকে ঠেলাও
  4. স্ট্যাকটি সাফ করুন (১) এবং নির্দেশিকা নির্দেশক (২)

উল্লেখযোগ্যভাবে, 4. এর অর্থ হল যে সাব্রোটিন সর্বদা একই অবস্থায় শুরু হয়। ফাংশনটির সাথে খোদাই করা সমস্ত কিছু শেষ হওয়ার পরে হারিয়ে যায়। পরে কোনও নির্দেশ থাকলেও কোনও ফাংশন পুনরায় শুরু করা যায় না return

root -\
  :    \- subfoo --\
  :/--<---return --/
  |
  V

১.২ অবিচ্ছিন্ন subroutines হিসাবে Coroutines

একটি কর্টিন সাব্রোটিনের মতো তবে এটি তার রাজ্যটি বিনষ্ট না করে প্রস্থান করতে পারে । এই মত একটি কর্টিন বিবেচনা করুন:

 def cofoo(bar):
      qux = yield bar  # yield marks a break point
      return qux

আপনি এটি চালানোর সময়, এর মানে হল

  1. barএবং জন্য স্ট্যাক স্থান বরাদ্দqux
  2. পুনরাবৃত্তভাবে প্রথম বিবৃতিটি কার্যকর করুন এবং পরবর্তী বিবৃতিতে ঝাঁপুন
    1. একবারে yield, তার মানটি কলিং স্ট্যাকের দিকে ঠেলাও তবে স্ট্যাক এবং নির্দেশ পয়েন্টারটি সঞ্চয় করুন
    2. একবার কল করার পরে yield, স্ট্যাক এবং নির্দেশ পয়েন্টারটি পুনরুদ্ধার করুন এবং এতে যুক্তি ধাক্কা দিনqux
  3. একবারে একবার return, এর মানটি কলিং স্ট্যাকের দিকে ঠেলাও
  4. স্ট্যাকটি সাফ করুন (১) এবং নির্দেশিকা নির্দেশক (২)

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():  # 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বিবৃতি।

২. একটি সাধারণ ইভেন্ট লুপের অ্যানাটমি

নিজেই, একটি কর্টিনের অন্য কোনও কর্টিনে নিয়ন্ত্রণ উত্পাদন করার কোনও ধারণা নেই । এটি কেবল কোনও করোটিন স্ট্যাকের নীচে কলারকে নিয়ন্ত্রণ দিতে পারে। এই কলারটি পরে অন্য কোনও কর্টিনে স্যুইচ করে এটি চালাতে পারে।

বেশ কয়েকটি কর্টিনের এই মূল নোডটি সাধারণত ইভেন্ট লুপ হয় : স্থগিতাদেশের পরে, কোনও কর্টিন একটি ইভেন্ট দেয় যা এটি পুনরায় শুরু করতে চায়। পরিবর্তে, ইভেন্ট লুপটি কার্যকরভাবে এই ইভেন্টগুলি হওয়ার জন্য অপেক্ষা করতে সক্ষম। এটি পরবর্তী কোন কর্টিন চালাবেন বা পুনরায় শুরু করার আগে কীভাবে অপেক্ষা করবেন তা সিদ্ধান্ত নিতে এটি আপনাকে অনুমতি দেয়।

এই জাতীয় নকশা বোঝায় যে পূর্বনির্ধারিত ইভেন্টগুলির একটি সেট রয়েছে যা লুপ বুঝতে পারে। awaitএকে অপরের বেশ কয়েকটি কর্টাইন থাকে, অবশেষে কোনও ইভেন্ট সম্পাদনা না হওয়া পর্যন্ত await। এই ইভেন্টটি নিয়ন্ত্রণের মাধ্যমে ইভেন্ট লুপের সাথে সরাসরি যোগাযোগ করতে পারে yield

loop -\
  :    \-> coroutine --await--> event --\
  :/ <-+----------------------- yield --/
  |    :
  |    :  # loop waits for event to happen
  |    :
  :\ --+-- 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

    # 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আমাদের ইভেন্ট 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. তাদের কাঙ্ক্ষিত জাগ্রত সময় অনুসারে কর্টাইনগুলি সাজান
  2. জেগে উঠতে চায় এমন প্রথমটি বেছে নিন
  3. এই সময় পর্যন্ত অপেক্ষা করুন
  4. এই কর্টিন চালান
  5. 1 থেকে পুনরাবৃত্তি।

তুচ্ছ বাস্তবায়নের জন্য কোনও উন্নত ধারণার প্রয়োজন হয় না। এ 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 সেকেন্ডের জন্য স্থগিত করে 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
            # 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। I / O পড়ার সাথে ইভেন্ট লুপকে উত্তেজিত করা

আমাদের ইভেন্ট লুপের ভিত্তি এখনও পূর্ব 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), [], [])

এটি আমাদের সমস্ত পঠনযোগ্য ফাইল দেয় - যদি কোনও থাকে তবে আমরা সংশ্লিষ্ট Coroutine চালাই। যদি কিছুই না থাকে তবে আমরা আমাদের বর্তমান কর্টিনটি চালানোর জন্য যথেষ্ট অপেক্ষা করেছি।

# 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। একসাথে রেখে

উপরেরটি কিছুটা সরলীকরণ ছিল। আমরা যদি সবসময় পড়তে পারি তবে ঘুমন্ত কর্টিনগুলিকে না খেয়ে থাকতে না করার জন্য আমাদের কিছু পরিবর্তন করতে হবে। আমাদের পড়ার মতো কিছুই নেই বা অপেক্ষা করার মতো কিছুই নেই handle যাইহোক, শেষ ফলাফলটি এখনও 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

৩.৫ সমবায় আই / ও

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পারে অনির্দিষ্টকালের অবরোধ । এটি ইভেন্ট লুপের সমস্ত কর্টিনগুলিকে ব্লক করে - যা খারাপ। লাইব্রেরি পছন্দaiofilesI / O এবং ফাইলের ইভেন্টগুলিতে জাল নন-ব্লক করার জন্য থ্রেড এবং সিঙ্ক্রোনাইজেশন ব্যবহারের ।

যাইহোক, সকেটগুলি I / O- অবরুদ্ধকরণের অনুমতি দেয় - এবং তাদের সহজাত বিলম্বিতা এটিকে আরও জটিল করে তোলে। ইভেন্ট লুপে ব্যবহৃত হওয়ার সময়, ডেটার জন্য অপেক্ষা এবং পুনরায় চেষ্টা করা কোনও কিছুকে ব্লক না করে মোড়ানো যায়।

4.1। অ-ব্লকিং I / O ইভেন্ট

আমাদের অনুরূপ AsyncRead, আমরা সকেটের জন্য স্থগিত ও পড়ার ইভেন্টটি সংজ্ঞায়িত করতে পারি। কোনও ফাইল নেওয়ার পরিবর্তে আমরা একটি সকেট নিয়ে যাই - এটি অবশ্যই অবরুদ্ধকরনীয়। এছাড়াও, পরিবর্তে আমাদের __await__ব্যবহার ।socket.recvfile.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 উপাদানযুক্ত একটি ইভেন্ট হিসাবে রিফ্যাক্টর করতে পারি। ফলস্বরূপ, ইভেন্ট লুপ, কর্টিনস এবং ইভেন্টগুলি পরিষ্কারভাবে একটি শিডিয়ুলার, স্বেচ্ছাসেবী ইন্টারমিডিয়েট কোড এবং প্রকৃত আই / ও আলাদা করে

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

সংযোজন

গিথুবে কোড উদাহরণ


yield selfঅ্যাসিঙ্কস্লিপ ব্যবহার করা আমাকে Task got back yieldত্রুটি দেয় , তা কেন? আমি দেখতে পাচ্ছি যে এসিএনসিও-ফিউচারের কোডটি এটি ব্যবহার করে। খালি ফলন ব্যবহার করা ভাল কাজ করে।
রন সেরুয়া

4
ইভেন্ট লুপগুলি সাধারণত তাদের নিজস্ব ইভেন্টগুলি আশা করে। আপনি সাধারণত গ্রন্থাগার জুড়ে ইভেন্ট এবং ইভেন্টের লুপগুলি মিশ্রিত করতে পারবেন না; এখানে প্রদর্শিত ইভেন্টগুলি কেবল প্রদর্শিত ইভেন্ট লুপের সাথে কাজ করে। সুনির্দিষ্টভাবে, অ্যাসিনসিও ইভেন্ট লুপের সংকেত হিসাবে কেবল কোনওটিই (অর্থাত্ একটি খালি ফলন) ব্যবহার করে না। অনুষ্ঠানগুলি ওয়েকআপগুলি নিবন্ধ করার জন্য ইভেন্ট লুপের সাথে সরাসরি যোগাযোগ করে।
মিস্টারমিয়াগি

12

আপনার coroডিজুয়ারিং ধারণাটি সঠিক, তবে কিছুটা অসম্পূর্ণ।

awaitনিঃশর্তভাবে স্থগিত করে না, তবে কেবলমাত্র যদি এটি একটি ব্লকিং কলের মুখোমুখি হয়। এটি কীভাবে জানতে পারে যে কোনও কল ব্লক করছে? কোডটি প্রতীক্ষিত হয়ে সিদ্ধান্ত নিয়েছে। উদাহরণস্বরূপ, সকেট রিডের একটি অপেক্ষাকৃত বাস্তবায়নের জন্য ডিজাইন করা যেতে পারে:

def read(sock, n):
    # sock must be in non-blocking mode
    try:
        return sock.recv(n)
    except EWOULDBLOCK:
        event_loop.add_reader(sock.fileno, current_task())
        return SUSPEND

বাস্তব অ্যাসিনসিওতে সমতুল্য কোডটিFuture ম্যাজিক মানগুলি ফিরিয়ে দেওয়ার পরিবর্তে রাষ্ট্রের পরিবর্তন করে তবে ধারণাটি একই। জেনারেটরের মতো বস্তুর সাথে যথাযথভাবে মানিয়ে নেওয়ার সময় উপরের কোডটি awaitএড করা যায়।

কলারের পাশে যখন আপনার কর্টিন থাকে:

data = await read(sock, 1024)

এটি কাছাকাছি কিছু মধ্যে desugars:

data = read(sock, 1024)
if data is SUSPEND:
    return SUSPEND
self.pos += 1
self.parts[self.pos](...)

জেনারেটরের সাথে পরিচিত লোকেরা উপরেরটি বর্ণনা করতে পারেন yield fromযা সাসপেনশন স্বয়ংক্রিয়ভাবে করে।

সাসপেনশন চেইনটি ইভেন্ট লুপ পর্যন্ত পুরোপুরি অব্যাহত থাকে, যেটি লক্ষ্য করে যে কর্টিন স্থগিত করা হয়েছে, এটি চলমান সেট থেকে সরিয়ে দেয়, এবং যদি চলমান থাকে তবে কার্টাইনগুলি চালানো যায়। যদি কোনও কর্টিনগুলি চলমান না select()থাকে তবে কোনও ফাইল বর্ণনাকারীর কোনও কর্টিন আইওয়ের জন্য প্রস্তুত না হওয়া পর্যন্ত লুপটি অপেক্ষা করে । (ইভেন্ট লুপটি কোনও ফাইল-বর্ণনাকারী-থেকে-কাউরোটিন ম্যাপিং বজায় রাখে))

উপরের উদাহরণে, একবার পড়ার যোগ্য select()ইভেন্ট লুপটি বলুন sock, এটি coroচলমান সেটটিতে পুনরায় যুক্ত হবে, সুতরাং এটি স্থগিতের বিন্দু থেকে চালিয়ে যাওয়া হবে।

অন্য কথায়:

  1. পূর্বনির্ধারিতভাবে একই থ্রেডে সবকিছু ঘটে।

  2. ইভেন্ট লুপটি করটিইনগুলি নির্ধারণ করার জন্য এবং তাদের জাগ্রত করার জন্য দায়ী, যখন তারা যা অপেক্ষা করছিল (সাধারণত একটি আইও কল যা সাধারণত অবরুদ্ধ হবে, বা একটি সময়সীমা) প্রস্তুত হয়ে যায়।

কর্টিন-ড্রাইভিং ইভেন্ট লুপগুলিতে অন্তর্দৃষ্টি করার জন্য, আমি ডেভ বিজলির এই আলাপের প্রস্তাব দিই , যেখানে তিনি লাইভ দর্শকের সামনে স্ক্র্যাচ থেকে একটি ইভেন্ট লুপ কোডিংয়ের প্রদর্শন করেন।


আপনাকে ধন্যবাদ, আমি যা করছি তার কাছাকাছি, তবে, এটি এখনও ব্যাখ্যা করে না যে কেন async.wait_for()এটি করা উচিত বলে না কেন ... ইভেন্ট লুপে একটি কলব্যাক যোগ করে এটি বলার এত বড় সমস্যা কেন? আপনি সবেমাত্র যুক্ত করেছেন এমনটি সহ, এটির প্রয়োজনীয় অনেকগুলি কলব্যাক প্রক্রিয়া করার জন্য? asyncioঅন্তর্নিহিত ধারণাটি খুব সাধারণ, এবং উদাহরণস্বরূপ, ইমাস লিস্প বয়সের জন্য বুজওয়ার্ডগুলি ব্যবহার না করেই বাস্তবায়ন করেছিল ... এর সাথে আমার হতাশার অংশটি রয়েছে ... (যেমন create-async-processএবং accept-process-output- এবং এটি যা প্রয়োজন তা ... (নিয়ন্ত্রিত)
wvxvw

10
@wvxvw আমি আপনার পোস্ট করা প্রশ্নের উত্তর দিতে যতটা সম্ভব পেরেছি, কেবলমাত্র শেষ অনুচ্ছেদে ছয়টি প্রশ্ন রয়েছে তা দেওয়া সম্ভব। এবং তাই আমরা এগিয়ে চলি - এটি যা wait_for করণীয় তা করে না (এটি করে, এটি এমন একটি কর্টিন যা আপনার প্রত্যাশিত) আমি মনে করি ইভেন্টটি লুপটি আলাদা থ্রেডে চলতে থাকলে আপনার সমস্যাটি অ্যাসিনসিওর সাথে মিলে যেতে পারে তবে আমি আপনার ব্যবহারের ক্ষেত্রে কীভাবে তার বিশদ জানি না এবং সত্যই, আপনার দৃষ্টিভঙ্গি আপনাকে সহায়তা করতে খুব মজাদার করে না।
ব্যবহারকারী 4815162342

5
@wvxvw My frustration with asyncio is in part due to the fact that the underlying concept is very simple, and, for example, Emacs Lisp had implementation for ages, without using buzzwords...- পাইথনের বাজেডওয়ার্ডস ছাড়া এই সাধারণ ধারণাটি বাস্তবায়িত করতে আপনাকে কোনও কিছুইই বাধা দেয় না :) আপনি কেন এই কুরুচিপূর্ণ অ্যাসিনসিওটিকে মোটেই ব্যবহার করেন না? স্ক্র্যাচ থেকে আপনার নিজের প্রয়োগ করুন। উদাহরণস্বরূপ, আপনি নিজের async.wait_for()ফাংশন তৈরির মাধ্যমে এটি শুরু করতে পারেন যা এটি যা যা করছিল ঠিক তা করে।
মিখাইল গেরাসিমভ

4
@ মিখাইল জিরাসিমভ আপনি মনে করছেন এটি একটি অলৌকিক প্রশ্ন। তবে, আমি আপনার জন্য রহস্যটি দূর করতে চাই। ভাষা অন্যদের সাথে কথা বলার জন্য ডিজাইন করা হয়েছে। আমি অন্যদের জন্য বেছে নিতে পারি না তারা কোন ভাষায় কথা বলে, এমনকি যদি আমি বিশ্বাস করি যে তারা যে ভাষায় কথা বলে তবে তা আবর্জনা, তবে আমি তাদের পক্ষে সবচেয়ে ভাল বোঝাতে পারি the অন্য কথায়, যদি আমি পছন্দ করে নিন বিনামূল্যে ছিলাম আমি পাইথন সঙ্গে, একা থাকতে দাও শুরু করার জন্য চয়ন না চাই asyncio। তবে, নীতিগতভাবে, এটি করার সিদ্ধান্ত আমার নয়। আমি en.wikedia.org/wiki/Ultimatum_game- এর মাধ্যমে আবর্জনা ব্যবহার করতে বাধ্য হয়েছি ।
wvxvw

4

এটি সমস্তই দু'টি মূল চ্যালেঞ্জকে ফোটায় যা অ্যাসিনসিও সম্বোধন করছে:

  • কীভাবে একক থ্রেডে একাধিক আই / ও সঞ্চালন করবেন?
  • সমবায় মাল্টিটাস্কিং কীভাবে বাস্তবায়ন করবেন?

প্রথম পয়েন্টটির উত্তর দীর্ঘকাল ধরে রয়েছে এবং তাকে নির্বাচিত লুপ বলা হয় । পাইথনে, এটি নির্বাচকদের মডিউলে প্রয়োগ করা হয় ।

দ্বিতীয় প্রশ্নটি কর্টিন ধারণার সাথে সম্পর্কিত , অর্থাত্ তাদের ফাংশনগুলি যা তাদের সম্পাদন বন্ধ করতে পারে এবং পরে পুনরুদ্ধার করা যায়। পাইথনে, জেনারেটর এবং বিবৃতি থেকে ফলন ব্যবহার করে কর্টিনগুলি প্রয়োগ করা হয় । এটাই হ'ল async / প্রতীক্ষিত বাক্যবিন্যাসের আড়ালে ।

এই উত্তরে আরও সংস্থান আছে ।


সম্পাদনা: গোরটাইন সম্পর্কে আপনার মন্তব্যে সম্বোধন:

অ্যাসিনসিওতে গোরোটিনের নিকটতম সমপরিমাণটি আসলে কোনও কর্টিন নয় বরং একটি কাজ ( ডকুমেন্টেশনের পার্থক্য দেখুন )। পাইথনে, কোনও কর্টিন (বা একটি জেনারেটর) ইভেন্ট লুপ বা আই / ও এর ধারণাগুলি সম্পর্কে কিছুই জানে না। এটি কেবল এমন একটি ফাংশন যা yieldএটির বর্তমান অবস্থা বজায় রেখে এটি কার্যকর করা বন্ধ করতে পারে, তাই এটি পরে পুনরুদ্ধার করা যায়। yield fromসিনট্যাক্স তাদের একটি স্বচ্ছ ভাবে chaining জন্য করতে পারবেন।

এখন, একটি অ্যাসিনসিও টাস্কের মধ্যে, চেইনের একেবারে নীচের অংশে কর্টিন সর্বদা ভবিষ্যতের ফলন শেষ করে । এই ভবিষ্যতের পরে ইভেন্ট লুপ পর্যন্ত বুদবুদ, এবং অভ্যন্তরীণ যন্ত্রপাতি একীভূত হয়। ভবিষ্যতের কিছু অন্য অভ্যন্তরীণ কলব্যাক দ্বারা সেট করা হয়ে গেলে ইভেন্ট লুপ ভবিষ্যতটিকে কর্টিন চেইনে ফেরত পাঠিয়ে কাজটি পুনরুদ্ধার করতে পারে।


সম্পাদনা: আপনার পোস্টে কিছু প্রশ্নের সমাধান:

এই দৃশ্যে আসলে আমি / ও কীভাবে ঘটে? আলাদা সুত্রে? পুরো দোভাষীটি স্থগিত হয়ে যায় এবং আমি / ও দোভাষী এর বাইরেও ঘটে?

না, থ্রেডে কিছুই হয় না। I / O সবসময় ইভেন্ট লুপ দ্বারা পরিচালিত হয়, বেশিরভাগ ফাইল বর্ণনাকারীর মাধ্যমে। তবে এই ফাইল বর্ণনাকারীদের নিবন্ধকরণটি সাধারণত আপনার জন্য নোংরা কাজ করে, উচ্চ-স্তরের করটিইনগুলি দ্বারা আড়াল করা হয়।

আই / ও দ্বারা ঠিক কী বোঝানো হয়েছে? যদি আমার অজগর পদ্ধতিটি সি ওপেন () পদ্ধতি বলে, এবং এটি কার্নেলের কাছে বাধা প্রেরণ করে, তার উপর নিয়ন্ত্রণ ত্যাগ করে, পাইথন দোভাষী কীভাবে এই সম্পর্কে জানতে পারে এবং অন্য কোনও কোড চালিয়ে যেতে সক্ষম হয়, যখন কার্নেল কোডটি আসল আই / ও এবং পাইথন পদ্ধতিটি জাগ্রত না করা পর্যন্ত যা বাধাটি পাঠিয়েছিল মূলত? নীতিগতভাবে পাইথন দোভাষী কীভাবে এই ঘটনার বিষয়ে সচেতন হতে পারেন?

একটি আই / ও হ'ল যে কোনও ব্লকিং কল। অ্যাসিনসিওতে সমস্ত আই / ও ক্রিয়াকলাপগুলি ইভেন্ট লুপের মধ্য দিয়ে যাওয়া উচিত, কারণ আপনি যেমনটি বলেছেন, ইভেন্ট লুপটির কোনও সচল কোডে একটি ব্লকিং কল করা হচ্ছে তা সচেতন হওয়ার কোনও উপায় নেই। এর অর্থ আপনি openকোনও কর্টিনের প্রসঙ্গে একটি সংলগ্ন ব্যবহার করার কথা নেই । পরিবর্তে, একটি ডেডিকেটেড লাইব্রেরি যেমন আইওফাইলগুলি ব্যবহার করুন যা এর একটি অ্যাসিনক্রোনাস সংস্করণ সরবরাহ করে open


বলছেন যে কর্টিনগুলি ব্যবহার yield fromকরে বাস্তবায়ন করা হয়েছে আসলে কিছুই বলে না। yield fromএটি কেবল একটি বাক্য গঠন, এটি কম্পিউটারগুলি চালিত করতে পারে এমন একটি মৌলিক বিল্ডিং ব্লক নয়। একইভাবে, সিলেক্ট লুপের জন্য। হ্যাঁ, গো-এর কর্টাইনগুলিও নির্বাচিত লুপ ব্যবহার করে তবে আমি যা করার চেষ্টা করছিলাম তা গোয়ে কাজ করবে তবে পাইথনে নয়। এটি কেন কাজ করে না তা বোঝার জন্য আমার আরও বিস্তারিত উত্তর দরকার।
wvxvw

দুঃখিত ... না, সত্যই নয়। "ভবিষ্যত", "টাস্ক", "স্বচ্ছ উপায়", "উপার্জন" কেবল বুজওয়ার্ডস, এগুলি প্রোগ্রামিংয়ের ডোমেনের বস্তু নয়। প্রোগ্রামিং এর ভেরিয়েবল, পদ্ধতি এবং কাঠামো রয়েছে। সুতরাং, "গোরোটিন একটি কাজ" বলতে গেলে এটি একটি বৃত্তাকারী বিবৃতি যা একটি প্রশ্ন উত্থাপন করে। পরিশেষে, asyncioআমার জন্য কী করে তার একটি ব্যাখ্যা সি কোডে ফুটে উঠবে যা চিত্রিত করে যে পাইথন সিনট্যাক্সটি কী অনুবাদ হয়েছিল।
wvxvw

আপনার উত্তর কেন আমার প্রশ্নের উত্তর দেয় না তা আরও ব্যাখ্যা করার জন্য: আপনার সরবরাহিত সমস্ত তথ্য সহ আমার লিঙ্কিত প্রশ্নে পোস্ট করা কোড থেকে আমার প্রচেষ্টা কেন কার্যকর হয়নি তা সম্পর্কে আমার কোনও ধারণা নেই। আমি একেবারে নিশ্চিত যে আমি ইভেন্ট লুপটি এমনভাবে লিখতে পারি যাতে এই কোডটি কার্যকর হয়। আসলে, আমি এইভাবে কোনও ইভেন্ট লুপ লিখতাম, যদি আমার একটি লিখতে হয়।
wvxvw

7
@wvxvw- এর সাথে আমি একমত নই সেগুলি "বুজওয়ার্ডস" নয় তবে উচ্চ স্তরের ধারণাগুলি যা অনেকগুলি লাইব্রেরিতে প্রয়োগ করা হয়েছে। উদাহরণস্বরূপ, একটি অ্যাসিনসিও টাস্ক, একটি জেনভেট গ্রিনলেট এবং গোরোটিন সমস্ত একই জিনিসটির সাথে মিলে যায়: একটি এক্সিকিউশন ইউনিট যা একক থ্রেডের সাথে একই সাথে চলতে পারে। এছাড়াও আমি মনে করি না সিটিকে অ্যাসিনসিওটি বোঝার জন্য আদৌ প্রয়োজন, যদি না আপনি পাইথন জেনারেটরের অভ্যন্তরীণ কাজগুলিতে যেতে চান।
ভিনসেন্ট

@wvxvw আমার দ্বিতীয় সম্পাদনা দেখুন। এটি উপায় থেকে দূরে কয়েকটি ভুল ধারণা পরিষ্কার করা উচিত।
ভিনসেন্ট
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.