برنامه نویسی آسنکرون در پایتون (asyncio)

سلام دوستان :) شاید شما هم "برنامه نویسی آسنکرون" یا "Asynchronous Programming" خیلی به گوشتون خورده باشه و براتون جالب باشه که بدونید چی هست. در این پُست قراره با هم دیگه راجع به این موضوع صحبت کنیم و نحوه پیاده سازی اون در زبان پایتون رو بررسی کنیم.گرچه ممکنه برنامه نویسی آسنکرون ممکنه در ابتدا نسبت به روش برنامه نویسی سنتی که تا حالا دیده بودیم سخت به نظر برسه اما برای ما efficiency خوبی رو فراهم می کنه. چطور؟برای این که به این سوال جواب بدیم بهتره اول یکم راجع به مفاهیم Blocking و Non Blocking توضیح بدم.انباری و جعبه شماره 4 !تمامی افراد داخل صف باید منتظر انجام کار نفر اول بمانند!فرض کنین کالایی رو سفارش دادین و بهتون گفتن حضوی بیاین تحویل بگیرین. وقتی میرسید به محل تحویل کالا میبینین که کلی آدم دیگه توی صف هستند و منتظرند کالای خودشون رو بگیرن. نوبت شما میشه و شما کالای شماره 4 رو میخواین تحویل بگیرید. مسئول تحویل بهتون میگه که کالای شما دم دست نیست و باید اون رو از انباری بیاره که فاصله اش با اینجا کم نیست در عین حالی که این مسیر رو پیاده میخواد بره!! در این حالت قطعا اعصابتون خورد میشه و مجبورین چند ساعتی رو منتظر بمونین!! در واقع نه تنها شما بلکه همه اونایی که پشت سر شما هستند هم باید منتظر بمونن. اگه نفر بعدی کالای شماره 3 رو بخواد و این کالا همون نزدیکی باشه باید صبر کنه تا کالای شما تحویلتون داده بشه و این اصلا منطقی نیست. در این حالت گفته میشه تَسک مربوط به شما Blocking هست، چون کل صف، معطل انجام کار شما هستند که زمان بر هست.حالا اگه یه سرویس تحویل کالا داشته باشیم که با خودرو برای مامور تحویل، کالا رو بیاره اینطور میشه که وقتی نوبت به شما میرسه مامور، درخواست شما رو ثبت می کنه و میگه کنار بایستید و میره سراغ نفر بعد. هر زمان که سفارش شما برسه تحویل داده میشه و بقیه هم معطل نمیمونن. در این حالت تَسک شما Non Blocking هست و درخواست های بعدی به سرعت بررسی میشن.ارجاع سفارش به سرویس تحویل کالا صف را از حالت blocking خارج می کندحالا اگه یک نرم افزار مثل یک وب سرور ساده رو در نظر بگیرید که فقط یک Thread داره و همه درخواست های وب به سمت اون میان، تا زمانی که یک تَسک خیلی بزرگ به سمتش نیاد کارش رو انجام میده و نتیجه رو به کلاینت بر می گردونه. حالا برای این که از مشکل blocking جلوگیری کنیم داخل پایتون میتونیم یک async coroutine (که در ادامه توضیح میدیم) ثبت کنیم و بریم سراغ پاسخگویی به بقیه درخواست ها. بقیه زبان ها هم از چنین قابلیتی برخوردارند.تابعی به نام coroutineیک تابع آسنکرون در داخل پایتون "coroutine" نامیده میشه و توسط کتابخانه استاندارد پایتون به نام asyncio پشتیبانی میشه. coroutine فقط یک فانکشنی هست که از کلمه کلیدی async استفاده می کنه و یا داخل asincio.coroutine@ اون رو decorate می کنیم. دو فانکشن زیر در واقع هر دو نوع تعریف coroutine رو نشون میدن :">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> ">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> این دو، توابع خاصی هستن که وقتی صدا زده بشن آبجکتی به نام coroutine object رو بر می گردونن. دقت کنید که صدا زدن این توابع به معنی اجرا شدن اون ها نیست و فقط coroutine object بر می گردونن که بعدا برای اجرا به event loop پاس داده میشن تا اجرا بشن.هر زمانی که بخواهیم چک کنیم یک تابع، coroutine هست یا نه کافیه از تابع asyncio.iscoroutinefunction(func) استفاده کنیم. برای تشخیص coroutine object هم میتونیم از تابع asyncio.iscoroutine(obj) استفاده کنیم.عبارت yield fromراه های مختلفی برای صدا زدن یک coroutine وجود داره. یک روش استفاده از عبارت yield from هست. این عبارت در Python 3.3 معرفی شد و بعدا در Python 3.5 روش async/await ارائه شد که استفاده از اون نسبت به yield from توصیه میشه. (در ادامه معرفی می کنیم)عبارت yield from به صورت زیر استفاده میشه :">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> ">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> همونطور که میبینید yield from داخل فانکشنی که با asyncio.coroutine@ ، مشخص شده نوشته شده. اگر این عبارت رو خارج از این فانکشن تعریف کنید با خطای زیر مواجه میشید :اگر از این سینتکس قراره استفاده کنین باید حتما اون رو داخل فانکشن دیگه ای که با asyncio.coroutine@ مشخص شده (decorate شده) قرار بدین.استفاده از async/awaitسینتکس جدیدتر و تمیز تر async/await هست. عبارت async برای معرفی کردن یک فانکشن به عنوان coroutine استفاده میشه (شبیه کاری که asyncio.coroutine@ برامون انجام میده). نحوه استفاده از اون به این شکل هست :برای صدا زدن این فانکشن، به جای yield from از await استفاده می کنیم. درست مانند yield from حتما باید داخل یک coroutine دیگه ازش استفاده کنیم وگرنه خطای سینتکسی میگیریم. اجرای event loopهمه مباحثی که تا حالا گفتیم بدون اجرا کردن event loop کار نخواهند کرد! event loop در واقع محل اصلی اجرای توابع آسنکرون است. بنابراین هر زمان که واقعا می خواهیم یک تابع آسنکرون رو اجرا کنیم باید از event loop استفاده کنیم.استفاده از event loop یک سری ویژگی در اختیارمون میذاره :رجیستر کردن، اجرا و کنسل کردن توابع آسنکرونایجاد قابلیت ارتباط بین کلاینت و سرورایجاد subprosess ها برای برقراری ارتباط با برنامه های دیگرمحول کردن فراخوانی توابع به استخری از thread هاساختار کلی برنامه ای که از asyncio استفاده می کنه چیزی شبیه به کد زیر هست :">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> ">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> سه خط آخر رو با هم بررسی می کنیم:خط اول یعنی ()asyncio.get_event_loop میاد event loop پیشفرض رو فراخوانی میکنه.خط دوم یعنی ()loop.run_until_complete تابعی از نوع blocking هست و تا وقتی که همه توابع آسنکرون اجرا نشن چیزی رو return نمی کنه.خط آخر یعنی ()loop.close هم زمانی که اجرای حلقه تموم شد، حلقه رو متوقف می کنه.استفاده از event loop وقتی که برنامه رو به چند thread میشکنیم بسیار کمک کننده است. حالتی رو در نظر بگیرید که thread اصلی، منطق برنامه رو جلو می بره و بقیه thread ها عملیات های سنگین IO رو انجام میدن.و اما یک مثالحالا که فهمیدیم چطور از توابع آسنکرون استفاده کنیم، بهتره یه مثال بزرگتر با هم انجام بدیم تا همه چیز واضح بشه. مثالی که در ادامه می زنیم یک برنامه آسنکرون هست که یک سری داده به صورت JSON از سایت reddit واکِشی می کنه، اون ها رو parse می کنه و top post های روزانه /r/python و /r/programming و /r/compsci رو برامون چاپ می کنه.اولین تابع یعنی ()get_json که توسط ()get_reddit_top صدا زده میشه یک درخواست GET به url مورد نظر میفرسته. وقتی این تابع با استفاده از عبارت await فراخوانی میشه، event loop به کار خودش ادامه میده و به coroutine های دیگه سرویس میده و در عین حال هر زمان پاسخ GET رسید اون رو بر میگردونه. وقتی پاسخ برگشت به تابع ()get_reddit_top تحویل میشه، parse میشه و بعدش چاپ میشه.این کد یه مقدار با کدی که اول دیدیم تفاوت داره. برای اجرا کردن چندین coroutine داخل event loop از تابع asyncio.ensure_future استفاده کردیم و بعد از اون event loop رو اجرا کردیم تا همه coroutine ها رو پردازش کنه.نکته : قبل از اجرای این برنامه لازمه که با دستور زیر aiohttp رو نصب کنید. aiohttp یک فریمورک کلاینت/سروری آسنکرون مبتنی بر HTTP هست.حالا اگر برنامه رو اجرا کنیم نتیجه به صورت زیر خواهد بود :">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> ">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">">"> توجه کنید که اگر چند مرتبه برنامه را اجرا کنیم، ترتیب subreddit ها متفاوت خواهد بود و این به خاطر این است که هر کدام از coroutine هایی که صدا زده می شوند thread ای را رها کنند آن thread به coroutine دیگر اختصاص پیدا می کند. یعنی هر کدام زودتر کارش تمام شود زودتر چاپ می شود.ببخشید یکم طولانی شد ;)ممنوناقتباس از : https://stackabuse.com/python-async-await-tutorial/ https://www.nginx.com/blog/thread-pools-boost-performance-9x/ https://docs.python.org/3/library/asyncio.html