اینترنت پنجره ها اندروید
بسط دادن

برنامه نویسی ناهمزمان نمونه های کد برنامه نویسی ناهمزمان نماینده های ناهمزمان c مرتب سازی

ناهمزمانی در برنامه نویسی

ایوان بوریسف

برنامه نویسی سنتی از برنامه نویسی همزمان استفاده می کند - اجرای متوالی دستورالعمل ها با فراخوانی های سیستم همزمان که به طور کامل رشته اجرا را مسدود می کند تا زمانی که یک عملیات سیستم مانند خواندن از روی دیسک کامل شود. به عنوان مثال، سرور اکو در زیر نوشته شده است:

while (true) (std:: string data; auto socket = Socket (localhost, port); socket.wait_connection(); while (!socket.end_of_connection()) ( data = socket.read(); // مسدود کردن سوکت نوشتن(داده)

هنگامی که متدهای read() و write() فراخوانی می شوند، رشته اجرایی فعلی در حین انتظار برای I/O شبکه قطع می شود. علاوه بر این، بیشتر اوقات برنامه به سادگی منتظر می ماند. در سیستم‌های با بارگذاری زیاد، این همان چیزی است که اغلب اتفاق می‌افتد - تقریباً در تمام مدت برنامه منتظر چیزی است: یک دیسک، یک DBMS، یک شبکه، یک UI، به طور کلی، یک رویداد خارجی مستقل از خود برنامه. در سیستم هایی که بارگذاری کمی دارند، می توان با ایجاد یک رشته جدید برای هر اقدام مسدود کننده، این مشکل را حل کرد. در حالی که یک رشته در حال خواب است، دیگری کار می کند.

اما وقتی تعداد کاربران زیاد است چه باید کرد؟ اگر حداقل یک رشته برای هر یک ایجاد کنید، عملکرد چنین سروری به شدت کاهش می یابد زیرا زمینه اجرای رشته به طور مداوم در حال تغییر است. همچنین، هر رشته دارای زمینه اجرای خاص خود از جمله حافظه پشته است که حداقل حجم آن 4 کیلوبایت است. برنامه نویسی ناهمزمان می تواند این مشکل را حل کند.

ناهمزمانی

ناهمزمانی در برنامه نویسی اجرای یک فرآیند در حالت فراخوانی سیستم غیر مسدود کننده است که به رشته برنامه اجازه می دهد تا پردازش را ادامه دهد. روش های مختلفی برای پیاده سازی برنامه نویسی ناهمزمان وجود دارد که در ادامه با آنها آشنا خواهید شد.

تماس های تلفنی

برای نوشتن یک برنامه ناهمزمان، می توانید از توابع پاسخ به تماس (از پاسخ به تماس انگلیسی - callback) استفاده کنید - توابعی که پس از تکمیل کار توسط برخی از کنترل کننده رویداد به صورت ناهمزمان فراخوانی می شوند. نمونه بازنویسی شده سرور با استفاده از توابع برگشت تماس:

در حالی که (درست) (سوکت خودکار = سوکت (لوکال هاست، پورت)؛ socket.wait_connection(); // socket.async_read هنوز مسدود شده است ((&داده خودکار) /* * رشته مسدود نشده است، تابع لامبدا فراخوانی می شود * هر بار پس از دریافت داده های جدید از سوکت، * و رشته اصلی برای ایجاد یک سوکت جدید و * منتظر اتصال جدید می شود */ ( socket.async_write(data, (auto &socket) ( if (socket.end_of_connection( )) socket.close( ) );

در wait_connection() ما هنوز منتظر چیزی هستیم، اما اکنون، در همان زمان، در داخل تابع wait_connection() می‌توان شباهتی از یک زمان‌بندی سیستم‌عامل پیاده‌سازی کرد، اما با توابع callback (در حالی که منتظر اتصال جدید هستیم، چرا به عنوان مثال، از طریق صف). تابع callback اگر داده های جدیدی در سوکت ظاهر شده باشد - lambda در async_read() یا داده نوشته شده باشد - lambda در async_write().

در نتیجه، ما عملیات ناهمزمان چندین اتصال را در یک رشته واحد دریافت کردیم، که خیلی کمتر منتظر می ماند. این ناهمزمانی را نیز می توان موازی کرد تا از استفاده از زمان CPU سود کامل به دست آورد.

چندین مشکل در این رویکرد وجود دارد. اولی را به شوخی جهنم برگشت به تماس می نامند. کافی است تصاویر این موضوع را در گوگل جستجو کنید تا متوجه شوید که چقدر ناخوانا و زشت است. در مثال ما فقط دو تابع تودرتو پاسخ به تماس وجود دارد، اما ممکن است تعداد بیشتری نیز وجود داشته باشد.

مشکل دوم این است که کد دیگر همزمان به نظر نمی رسد: "پرش" از wait_connection() به lambda وجود دارد، مانند لامبدا که به async_write() ارسال می شود، که دنباله کد را می شکند و پیش بینی به چه ترتیبی را غیرممکن می کند. لامبدا نامیده می شود. این کار خواندن و درک کد را دشوار می کند.

Async/Await

بیایید سعی کنیم کدهای ناهمزمان را طوری بسازیم که شبیه همگام به نظر برسد. برای درک بهتر، اجازه دهید کار را کمی تغییر دهیم: اکنون باید داده ها را از DBMS و یک فایل با استفاده از یک کلید ارسال شده از طریق شبکه بخوانیم و نتیجه را از طریق شبکه ارسال کنیم.

public async void work() ( var db_conn = Db_connection (localhost)؛ var socket = Socket (localhost, port); socket.wait_connection(); var data = socket.async_read(); var db_data = db_conn.async_get (انتظار داده ها) ;

بیایید خط به خط برنامه را مرور کنیم:

  • کلمه کلیدی async در هدر تابع به کامپایلر می گوید که تابع ناهمزمان است و باید به گونه ای متفاوت کامپایل شود. نحوه دقیق انجام این کار در زیر نوشته شده است.
  • سه خط اول تابع: ایجاد و انتظار برای اتصال.
  • خط بعدی خواندن ناهمزمان را بدون وقفه در رشته اصلی اجرا انجام می دهد.
  • دو خط بعدی یک درخواست ناهمزمان به پایگاه داده می دهند و فایل را می خوانند. عملگر await عملکرد فعلی را متوقف می کند تا زمانی که کار ناهمزمان خواندن از پایگاه داده و فایل کامل شود.
  • آخرین خطوط نوشتن ناهمزمان را در سوکت انجام می دهند، اما تنها پس از اینکه منتظر خواندن ناهمزمان از پایگاه داده و فایل باشیم.

این سریعتر از انتظار متوالی ابتدا برای پایگاه داده و سپس برای فایل است. در بسیاری از پیاده سازی ها، عملکرد async / await بهتر از عملکردهای کلاسیک بازگشت به تماس است، در حالی که چنین کدهایی به صورت همزمان خوانده می شوند.

کوروتین ها

مکانیسمی که در بالا توضیح داده شد کوروتین نامیده می شود. شما اغلب می توانید نوع "coroutine" را بشنوید (از انگلیسی coroutine - coroutine).

چندین نقطه ورودی

اساساً کوروتین ها توابعی هستند که دارای چندین نقطه ورود و خروج هستند. توابع معمولی فقط یک نقطه ورود و چندین نقطه خروج دارند. اگر به مثال بالا برگردیم، اولین نقطه ورود، فراخوانی خود تابع با استفاده از عملگر async خواهد بود، سپس تابع به جای اینکه منتظر پایگاه داده یا فایل باشد، اجرای خود را قطع خواهد کرد. تمام انتظارهای بعدی عملکرد را مجدداً راه اندازی نمی کنند، اما اجرای آن را در نقطه وقفه قبلی ادامه می دهند. بله، در بسیاری از زبان‌ها می‌تواند چندین انتظار در یک کوروتین وجود داشته باشد.

برای درک بهتر، بیایید به کد موجود در پایتون نگاه کنیم:

Def async_factorial(): result = 1 در حالی که True: نتیجه نتیجه *= i fac = async_factorial() برای i در محدوده (42): print(next(fac))

برنامه کل دنباله اعداد فاکتوریل را با اعداد از 0 تا 41 خروجی می دهد.

تابع async_factorial() یک شی مولد را برمی گرداند که می تواند به تابع () next ارسال شود، که اجرای coroutine را تا دستور بازده بعدی ادامه می دهد و وضعیت همه متغیرهای تابع محلی را حفظ می کند. تابع ()next آنچه را که عبارت yield در داخل کوروتین ارسال می کند، برمی گرداند. بنابراین تابع async_factorial() در تئوری دارای چندین نقطه ورودی و خروجی است.

Stackful و Stackless

بسته به استفاده از پشته، کوروتین ها به دو دسته Stackful تقسیم می شوند که در آن هر coroutine دارای پشته خاص خود است و stackless که در آن همه متغیرهای تابع محلی در یک شی خاص ذخیره می شوند.

از آنجایی که در کوروتین‌ها می‌توانیم یک عبارت بازدهی را در هر جایی قرار دهیم، باید کل بافت تابع را در جایی ذخیره کنیم، که شامل فریم روی پشته (متغیرهای محلی) و سایر اطلاعات متا است. این را می توان برای مثال با جایگزین کردن کامل پشته، همانطور که در کوروتین های پشته ای انجام می شود، انجام داد.

در شکل زیر، فراخوانی async یک قاب پشته جدید ایجاد می کند و اجرای thread را به آن تغییر می دهد. این عملا یک موضوع جدید است، فقط به صورت ناهمزمان با اصلی اجرا می شود.

yield به نوبه خود قاب پشته قبلی را برای اجرا برمی گرداند و مرجعی را به انتهای قاب فعلی در پشته قبلی ذخیره می کند.

داشتن پشته اختصاصی به شما امکان می دهد از فراخوانی های تابع تو در تو تسلیم شوید، اما چنین تماس هایی با ایجاد/تغییر کامل زمینه اجرای برنامه همراه است که کندتر از برنامه های بدون پشته است.

مولدتر، اما در عین حال محدودتر، روال های بدون پشته هستند. آنها از پشته استفاده نمی کنند و کامپایلر یک تابع حاوی کوروتین ها را به یک ماشین حالت بدون کوروتین تبدیل می کند. به عنوان مثال، کد:

Def fib(): a = 0 b = 1 در حالی که True: بازده a += b بازده b b += a

به شبه کد زیر تبدیل می شود:

کلاس fib: def __init__(self): self.a = 0 self.b = 1 self.__result: int self.__state = 0 def __next__(self): while True: if self.__state == 0: self.a = 0 self.b = 1 if self.__state == 0 or self.__state == 3: self.__result = self.a self.__state = 1 self.__state if self.__state == 1: self.a += self.b self.__result = self.b self.__state = 2 خود را برگرداند.__نتیجه اگر خود.__state == 2: self.b += یک خود.__حالت = 3 شکستن

در اصل، این یک کلاس ایجاد می کند که تمام وضعیت تابع، و همچنین آخرین نقطه ای که در آن بازده فراخوانی شده است را ذخیره می کند. یک مشکل در این رویکرد وجود دارد: بازده را فقط می توان در بدنه یک تابع کوروتین فراخوانی کرد، اما نه از درون توابع تودرتو.

متقارن و نامتقارن

کوروتین ها نیز به متقارن و نامتقارن تقسیم می شوند.

متقارنیک برنامه زمانبندی جهانی داشته باشید که از بین تمام عملیات ناهمزمان معلق، عملیاتی را که باید بعدا اجرا شود انتخاب می کند. یک مثال زمانبندی است که در ابتدای تابع wait_connection() بحث شده است.

که در نامتقارنکوروتین ها زمان بندی جهانی ندارند و برنامه نویس به همراه پشتیبانی کامپایلر انتخاب می کند که کدام برنامه و زمان اجرا شود. اکثر پیاده سازی های کوروتین نامتقارن هستند.

نتیجه

برنامه نویسی ناهمزمان ابزار بسیار قدرتمندی برای بهینه سازی برنامه های پر بار با انتظار سیستمی مکرر است. اما مانند هر فناوری پیچیده، نمی توان از آن فقط به دلیل وجود آن استفاده کرد. همیشه باید این سوال را از خود بپرسید: آیا من به این فناوری نیاز دارم؟ چه مزایای عملی به من می دهد؟ در غیر این صورت، توسعه دهندگان در معرض خطر صرف تلاش، زمان و پول زیادی بدون دریافت سود هستند.

حاشیه نویسی: برنامه نویسی ناهمزمان در دات نت فریم ورک. روش‌های EndOperation، Pooling، Callback. راه اندازی ناهمزمان یک روش دلخواه. به روز رسانی رابط. امنیت برنامه های چند رشته ای همگام سازی: خودکار، دستی؛ استفاده از مناطق همگام سازی کنترل ProgressBar

هنگامی که ما یک متد طولانی را اجرا می کنیم، پردازنده آن را اجرا می کند، در حالی که "زمانی" برای انجام سایر درخواست های کاربر نخواهد داشت (شکل 7.2).

هنگام اجرای همزمان، برنامه فقط یک رشته دارد. با استفاده از مدل ناهمزماندر برنامه نویسی، می توانید چندین رشته موازی را اجرا کنید و در حین اجرا به اقدامات جدید کاربر واکنش نشان دهید. هنگامی که n-thread اجرا شد، نتیجه را روی صفحه نمایش می دهید.

در زندگی روزمره نمونه های زیادی از رفتار ناهمزمان وجود دارد - پر کردن مجدد مواد خام در یک انبار به کارخانه اجازه می دهد تا از عملکرد بی وقفه اطمینان حاصل کند. یک مدل غیرهمگام، استفاده کامل از مواد خام و متعاقب آن خرابی در حین انتظار برای تحویل مواد است.

پشتیبانی از برنامه نویسی ناهمزمان در دات نت فریم ورک

کلاس هایی که پشتیبانی داخلی دارند مدل ناهمزمان، یک جفت روش ناهمزمان برای هر یک از متدهای سنکرون داشته باشید. این روش ها با کلمات Begin و End شروع می شوند. برای مثال، اگر بخواهیم از نسخه ناهمزمان متد Read کلاس System.IO.Stream استفاده کنیم، باید از متدهای BeginRead و EndRead همان کلاس استفاده کنیم.

برای استفاده از پشتیبانی داخلی مدل ناهمزمانبرنامه نویسی، باید روش BeginOperation مناسب را فراخوانی کرده و مدل تکمیل تماس را انتخاب کنید. فراخوانی متد BeginOperation یک شی رابط IAsyncResult را برمی گرداند که وضعیت اجرای عملیات ناهمزمان را تعیین می کند. روش های مختلفی برای خاتمه دادن به روش های ناهمزمان وجود دارد.

روش EndOperation

روش EndOperation برای پایان دادن به فراخوانی ناهمزمان استفاده می‌شود، زمانی که thread اصلی نیاز به انجام محاسبات زیادی دارد که مستقل از نتایج فراخوانی روش ناهمزمان است. پس از اینکه کار اصلی انجام شد و برنامه برای اقدامات بعدی به نتایج روش ناهمزمان نیاز داشت، روش EndOperation فراخوانی می شود. در این حالت، نخ اصلی تا زمانی که روش ناهمزمان کامل شود، معلق خواهد بود. نمونه ای از استفاده از این روش:

استفاده از سیستم؛ با استفاده از System.IO؛ فضای نام EndOperation ( class Class1 ( static void Main(string args) ( //یک جریان ایجاد کنید و فایل را باز کنید. FileStream fs = New FileStream("text.txt", FileMode.Open)؛ byte fileBytes = بایت جدید؛ // اجرا متد Read در یک جریان موازی IAsyncResult ar = fs.BeginRead(fileBytes, 0, fileBytes.Length, null, null for(int i = 0; i);<10000000; i++) { // Имитация длительной работы основного // потока, не зависящая от выполнения асинхронного метода. } // После завершения работы основного потока // запускаем завершение выполнения параллельного // метода Read. fs.EndRead(ar); Console.Write(System.Text.Encoding.Default.GetString(fileBytes)); } } } Листинг 7.1.

برای پایان دادن به اجرای thread ar موازی، متد EndRead در اینجا فراخوانی شد. به عنوان کدی که کار درازمدت را شبیه‌سازی می‌کند، می‌توانید شمارنده دقیق تکمیل کار را که در «استفاده از کتابخانه‌های کد در فرم‌های ویندوز» به آن پرداختیم، متصل کنید.

روش نظرسنجی

این روش برای مواردی مناسب است که شما نیاز به کنترل اجرای یک روش ناهمزمان دارید. هنگام استفاده از آن، باید اجرای متد ناهمزمان را به صورت دستی و با استفاده از ویژگی IsCompleted یک شی از نوع IAsyncResult بررسی کنید. این رایج ترین تکنیک خاتمه نیست زیرا اکثر فرآیندها به کنترل اجرا نیاز ندارند. مثال استفاده از روش نظرسنجی:

استفاده از سیستم؛ با استفاده از System.IO؛ ادغام فضای نام (کلاس Class1 (حذف ثابت Main(رشته آرگ) (FileStream fs = New FileStream("text.txt"، FileMode.Open)؛ بایت fileBytes = بایت جدید؛ Console.Write ("روش خواندن را به صورت ناهمزمان اجرا کنید.") ; // روش Read را به صورت ناهمزمان اجرا کنید. خواندن فایل Console.Write("Process in progress");

به احتمال زیاد، در طول فرآیند متوجه ظاهر کتیبه نخواهید شد - فایل خیلی سریع خوانده می شود. برای آزمایش این روش، یک فایل متنی بزرگ در فلاپی دیسک بنویسید، آدرس را مشخص کنید و دوباره برنامه را اجرا کنید.

روش برگشت به تماس

روش Callback برای پایان دادن به تماس ناهمزمان در مواردی استفاده می شود که می خواهید از مسدود شدن رشته اصلی جلوگیری کنید. هنگام استفاده از Callback متد EndOperation را در بدنه متد اجرا می کنیم که زمانی فراخوانی می شود که متد در حال اجرا در یک رشته موازی کامل شود. امضای متدی که فراخوانی می شود باید با امضای نماینده AsyncCallback مطابقت داشته باشد.

نمونه ای از استفاده از گزینه Callback:

استفاده از سیستم؛ با استفاده از System.IO؛ فضای نام Callback ( class Class1 ( // ایجاد یک جریان و آرایه ای از بایت ها. FileStream ثابت fs؛ بایت ثابت فایل بایت؛ خالی ثابت اصلی (رشته آرگ) ( // باز کردن فایل در یک جریان. fs = new FileStream("text. txt"، FileMode. Open؛ fileBytes = بایت جدید؛ // اجرای متد Read به صورت ناهمزمان با عبور از متد WorkComplete fs.BeginRead(fileBytes, 0, (int)fs.Length, new AsyncCallback(WorkComplete), null ) // /

/// هنگامی که رشته موازی خاتمه می یابد، روش به طور خودکار فراخوانی می شود. /// /// شی از نوع IAsyncResult. static void WorkComplete(IAsyncResult ar) (// پایان روش را شروع کنید. fs.EndRead(ar); string textFromFile = System.Text.Encoding.Default.GetString(fileBytes); Console.Write(textFromFile); ) ) لیست 7.3.

در دیسک همراه کتاب، برنامه های EndOperation، Pooling و Callback (Code\Glava7\EndOperation، Pooling، Callback) را خواهید یافت.

آخرین به روز رسانی: 1397/10/17

Asynchrony به شما این امکان را می دهد که وظایف فردی را از رشته اصلی به روش های ناهمزمان خاص یا بلوک های کد منتقل کنید. این به ویژه در برنامه های گرافیکی صادق است، جایی که کارهای طولانی می توانند رابط کاربری را مسدود کنند. و برای جلوگیری از این اتفاق، باید از ناهمزمان استفاده کنید. Asynchrony همچنین دارای مزایایی در برنامه های کاربردی وب هنگام پردازش درخواست های کاربران، هنگام دسترسی به پایگاه های داده یا منابع شبکه است. برای پرس و جوهای بزرگ به پایگاه داده، روش ناهمزمان به سادگی برای مدتی به خواب می رود تا زمانی که داده ها را از پایگاه داده دریافت کند و رشته اصلی می تواند به کار خود ادامه دهد. در یک برنامه همزمان، اگر کد دریافت داده در رشته اصلی باشد، این رشته به سادگی هنگام دریافت داده مسدود می شود.

کلید کار با تماس های ناهمزمان در سی شارپ دو کلمه کلیدی است: async و await که هدف از آن آسان کردن نوشتن کدهای ناهمزمان است. آنها با هم برای ایجاد یک روش ناهمزمان استفاده می شوند.

روش ناهمزماندارای ویژگی های زیر است:

    هدر متد از اصلاح کننده async استفاده می کند

    متد حاوی یک یا چند عبارت انتظار است

    نوع بازگشت یکی از موارد زیر است:

    • ValueTask

یک روش ناهمزمان، مانند یک روش معمولی، می تواند از هر تعداد پارامتر استفاده کند یا اصلاً از آنها استفاده نکند. با این حال، یک روش ناهمزمان نمی تواند پارامترها را با اصلاح کننده های out و ref تعریف کند.

همچنین شایان ذکر است که کلمه async که در تعریف متد مشخص شده است، به طور خودکار متد را ناهمزمان نمی کند. این به سادگی نشان می دهد که متد ممکن است حاوی یک یا چند عبارت انتظار باشد.

بیایید به مثالی از یک روش ناهمزمان نگاه کنیم:

استفاده از سیستم؛ با استفاده از System.Threading. با استفاده از System.Threading.Tasks. فضای نام HelloApp ( کلاس برنامه ( static void Factorial() ( int result = 1; for(int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { Console.WriteLine("Начало метода FactorialAsync"); // выполняется синхронно await Task.Run(()=>فاکتوریال()); // به صورت ناهمزمان اجرا شد Console.WriteLine("End of FactorialAsync متد"); ) static void Main(string args) ( FactorialAsync(); // فراخوانی متد ناهمزمان Console.WriteLine("عددی را وارد کنید:")؛ int n = Int32.Parse(Console.ReadLine())؛ Console.WriteLine($ "عدد مربع برابر است با (n * n)");

در اینجا ابتدا روش معمول محاسبه فاکتوریل تعریف می شود. برای شبیه‌سازی زمان‌های طولانی، با استفاده از روش ()Thread.Sleep از تاخیر 8 ثانیه‌ای استفاده می‌کند. به طور معمول، این روشی است که برخی از کارها را در مدت زمان طولانی انجام می دهد. اما برای درک آسان تر، به سادگی فاکتوریل 6 را محاسبه می کند.

همچنین متد ناهمزمان FactorialAsync() را تعریف می کند. ناهمزمان است زیرا اصلاحگر async را در جلوی نوع بازگشتی در تعریف دارد، نوع برگشتی آن خالی است و عبارت await در بدنه متد تعریف شده است.

عبارت await وظیفه ای را مشخص می کند که به صورت ناهمزمان اجرا می شود. در این مورد، یک کار مشابه اجرای تابع فاکتوریل را نشان می دهد:

Await Task.Run(()=>Factorial());

طبق قوانین ناگفته، مرسوم است که از پسوند Async - FactorialAsync () به نام روش های ناهمزمان استفاده شود، اگرچه در اصل این امر ضروری نیست.

ما خود فاکتوریل را در روش ناهمزمان FactorialAsync دریافت می کنیم. ناهمزمان است زیرا با اصلاح کننده async اعلان شده است و حاوی استفاده از کلمه کلیدی await است.

و در متد Main به این متد آسنکرون می گوییم.

بیایید ببینیم خروجی کنسول برنامه چه خواهد بود:

شروع روش FactorialAsync یک عدد وارد کنید: 7 مربع عدد 49 است انتهای متد Main Factorial 720 است پایان روش FactorialAsync

بیایید گام به گام به آنچه در اینجا اتفاق می افتد نگاه کنیم:

    متد Main اجرا می شود که متد FactorialAsync ناهمزمان را فراخوانی می کند.

    متد FactorialAsync تا زمان عبارت await شروع به اجرای همزمان می کند.

    عبارت await یک کار ناهمزمان را اجرا می کند Task.Run(()=>Factorial())

    در حالی که وظیفه ناهمزمان Task.Run(()=>Factorial()) در حال اجرا است (و می تواند برای مدت طولانی اجرا شود)، اجرای کد به روش فراخوانی باز می گردد - یعنی به متد Main. در روش Main از ما خواسته می شود که یک عدد برای محاسبه مجذور عدد وارد کنیم.

    این مزیت روش‌های ناهمزمان است - یک کار ناهمزمان که می‌تواند برای مدت طولانی اجرا شود، روش Main را مسدود نمی‌کند و ما می‌توانیم به کار با آن ادامه دهیم، به عنوان مثال، وارد کردن و پردازش داده‌ها.

    هنگامی که کار ناهمزمان اجرای خود را کامل کرد (در مورد بالا فاکتوریل یک عدد را محاسبه کرد)، روش FactorialAsync ناهمزمان که وظیفه ناهمزمان نامیده می شود، به کار خود ادامه می دهد.

تابع فاکتوریل ممکن است بهترین مثال نباشد، زیرا در واقعیت هیچ فایده ای برای ناهمزمان کردن آن در این مورد وجود ندارد. اما بیایید به مثال دیگری نگاه کنیم - خواندن و نوشتن یک فایل:

استفاده از سیستم؛ با استفاده از System.Threading. با استفاده از System.Threading.Tasks. با استفاده از System.IO؛ namespace HelloApp ( class Program ( static async void ReadWriteAsync() ( string s = "Hello world! One step at a time"; // hello.txt - فایلی که با استفاده از (StreamWriter writer = new StreamWriter(" hello .txt، false)) ( await writer.WriteLineAsync(ها)؛ // نوشتن ناهمزمان در فایل ) با استفاده از (StreamReader reader = StreamReader جدید("hello.txt")) (نتیجه رشته = await reader.ReadToEndAsync() ; / / خواندن ناهمزمان از فایل Console.WriteLine(نتیجه) ) static void Main(string args) ( ReadWriteAsync() .

متد ناهمزمان ReadWriteAsync() رشته ای را در فایل می نویسد و سپس فایل نوشته شده را می خواند. چنین عملیاتی می تواند زمان زیادی را به طول بینجامد، به خصوص با حجم زیاد داده، بنابراین بهتر است چنین عملیاتی به صورت ناهمزمان انجام شود.

فریم ورک دات نت قبلاً برای چنین عملیاتی پشتیبانی داخلی دارد. به عنوان مثال، کلاس StreamWriter یک متد WriteLineAsync() را تعریف می کند. در واقع، از قبل یک عملیات ناهمزمان را نشان می دهد و رشته خاصی را که باید در یک فایل نوشته شود، به عنوان پارامتر می گیرد. از آنجایی که این متد یک عملیات ناهمزمان را نشان می‌دهد، می‌توانیم فراخوانی این متد را به عنوان یک عبارت انتظار قاب کنیم:

Await writer.WriteLineAsync(s); // نوشتن ناهمزمان در یک فایل

به طور مشابه، کلاس StreamReader یک متد ReadToEndAsync() را تعریف می کند که همچنین یک عملیات ناهمزمان را نشان می دهد و تمام متن خوانده شده را برمی گرداند.

چارچوب .NET Core روش های مشابه زیادی را تعریف می کند. به عنوان یک قاعده، آنها با کار با فایل ها، ارسال درخواست های شبکه یا پرس و جوهای پایگاه داده مرتبط هستند. آنها به راحتی با پسوند Async شناخته می شوند. یعنی اگر متدی در نام خود پسوندی مشابه داشته باشد، احتمال استفاده از آن در عبارت انتظار بیشتر است.

Static void Main(string args) ( ReadWriteAsync(); Console.WriteLine ("Some Work"); Console.Read(); )

مجدداً، هنگامی که اجرای در متد ReadWriteAsync به اولین عبارت انتظار می‌رسد، کنترل به متد Main برمی‌گردد و می‌توانیم کار با آن را ادامه دهیم. نوشتن روی فایل و خواندن فایل به صورت موازی انجام می شود و عملکرد متد Main را مسدود نمی کند.

تعریف عملیات ناهمزمان

همانطور که در بالا ذکر شد، فریم ورک دات نت Core دارای روش های داخلی بسیاری است که یک عملیات ناهمزمان را نشان می دهد. آنها با پسوند Async پایان می یابند. و قبل از فراخوانی چنین متدهایی می توانیم عملگر await را مشخص کنیم. مثلا:

StreamWriter writer = new StreamWriter("hello.txt", false); await writer.WriteLineAsync("Hello"); // نوشتن ناهمزمان در یک فایل

یا می توانیم با استفاده از متد ()Task.Run یک عملیات ناهمزمان تعریف کنیم:

Static void Factorial() ( int result = 1; for (int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { await Task.Run(()=>فاکتوریال()); // فراخوانی یک عملیات ناهمزمان)

شما می توانید یک عملیات ناهمزمان را با استفاده از عبارت lambda تعریف کنید:

Static async void FactorialAsync() ( await Task.Run(() => ( int result = 1; for (int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); }); }

انتقال پارامترها به یک عملیات ناهمزمان

در بالا فاکتوریل 6 را محاسبه کردیم، اما فرض کنید می خواهیم فاکتوریل اعداد مختلف را محاسبه کنیم:

استفاده از سیستم؛ با استفاده از System.Threading. با استفاده از System.Threading.Tasks. فضای نام HelloApp ( کلاس برنامه ( static void Factorial(int n) ( int result = 1; for (int i = 1; i<= n; i++) { result *= i; } Thread.Sleep(5000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync(int n) { await Task.Run(()=>فاکتوریل (n)); ) static void Main(string args) ( FactorialAsync(5)؛ FactorialAsync(6)؛ Console.WriteLine("Some Work"); Console.Read(); ) ) )

دریافت نتیجه از یک عملیات ناهمزمان

یک عملیات ناهمزمان می‌تواند نتایجی را برگرداند که می‌توانیم به همان روشی که هنگام فراخوانی یک متد معمولی به دست آوریم:

استفاده از سیستم؛ با استفاده از System.Threading. با استفاده از System.Threading.Tasks. فضای نام HelloApp ( کلاس برنامه ( static int Factorial(int n) ( int result = 1; for (int i = 1; i<= n; i++) { result *= i; } return result; } // определение асинхронного метода static async void FactorialAsync(int n) { int x = await Task.Run(()=>فاکتوریل (n)); Console.WriteLine($"Factorial is (x)"); ) static void Main(string args) ( FactorialAsync(5)؛ FactorialAsync(6); Console.Read(); ) ) )

متد Factorial مقداری از نوع int را برمی گرداند، ما می توانیم این مقدار را با اختصاص دادن نتیجه یک عملیات ناهمزمان به متغیری از این نوع بدست آوریم: int x = await Task.Run(()=>Factorial(n));

مدتی است که من سوالات زیادی در مورد برنامه نویسی ناهمزمان دریافت می کنم. و با درک اینکه این موضوع مورد توجه بسیاری از خوانندگان من است، تصمیم گرفتم مقاله ای برای توضیح این اصطلاحات بنویسم، به خصوص که برنامه نویسی ناهمزمان بخش بسیار مهمی از اینترنت مدرن است.

برای شروع، باید توجه داشت که دو مفهوم کاملاً متفاوت وجود دارد: اولی مدل های برنامه نویسی همزمان و ناهمزمان، و دوم - محیط های تک رشته ای و چند رشته ای. هر یک از مدل های برنامه نویسی (همگام و ناهمزمان) می توانند در محیط های تک رشته ای و چند رشته ای کار کنند.

مدل برنامه نویسی همزمان

در این مدل برنامه نویسی یک thread به یک وظیفه اختصاص داده شده و شروع به کار روی آن می کند. پس از تکمیل یک کار، موضوع برای کار بعدی در دسترس است. آن ها یک کار به ترتیب با دیگری جایگزین می شود. در این مدل نمی توان یک کار را وسط گذاشت تا کار دیگری را انجام دهد. بیایید در مورد نحوه عملکرد این مدل در محیط های تک رشته ای و چند رشته ای بحث کنیم.

محیط تک رشته ای- اگر چند کار داشته باشیم که باید اجرا شوند و سیستم فعلی فقط یک رشته را ارائه می دهد، وظایف یک به یک به thread اختصاص داده می شود. این را می توان به صورت بصری به صورت زیر نشان داد:

جایی که موضوع 1 -یک جریان، وظیفه 1 و وظیفه 2، وظیفه 3، وظیفه 4 -وظایف مربوطه

می بینیم که یک جریان داریم ( موضوع 1) و چهار کارکه باید تکمیل شوند یک رشته شروع به کار روی وظایف می کند و همه کارها را یکی یکی تکمیل می کند.

محیط چند رشته ای - Multi-Threaded- در این محیط از چندین رشته استفاده می کنیم که می توانند این وظایف را به طور همزمان انجام دهند. این به این معنی است که ما داریم استخر نخ(بر اساس منابع موجود می توان رشته های جدید نیز ایجاد کرد) و چندین کار.

می بینیم که چهار رشته و به همان تعداد کار داریم. بنابراین، هر رشته یک کار را انجام می دهد و آن را کامل می کند. این یک سناریوی ایده‌آل است، اما در شرایط عادی ما بیشتر از تعداد رشته‌های موجود وظایف داریم. و بنابراین، هنگامی که یک موضوع اجرای یک کار را به پایان می رساند، بلافاصله شروع به اجرای دیگری می کند. همچنین توجه داشته باشید که هر بار یک رشته جدید ایجاد نمی شود زیرا به منابع سیستم مانند چرخه های CPU و حافظه نیاز دارد که ممکن است کافی نباشد.

حال بیایید در مورد مدل ناهمزمان و نحوه رفتار آن در محیط تک رشته ای و چند رشته ای صحبت کنیم.

مدل برنامه نویسی ناهمزمان

برخلاف مدل برنامه نویسی همزمان، در اینجا یک رشته، با شروع یک کار خاص، می تواند اجرای آن را برای مدت معینی متوقف کند، در حالی که وضعیت فعلی خود را حفظ کرده، و شروع به اجرای کار دیگری کند.

می بینیم که یک رشته مسئول اجرای تمام وظایف و جایگزین کردن آنها با یکدیگر است.

اگر سیستم ما قادر به ایجاد چندین رشته باشد، آنگاه همه رشته ها می توانند در یک مدل ناهمزمان کار کنند.

ما می بینیم که همان وظایف T4، T5، T6توسط چندین رشته پردازش می شود. این زیبایی و قدرت این فیلمنامه است. همانطور که می بینید، وظیفه T4برای اولین بار در موضوع راه اندازی شد موضوع 1و در تاپیک تکمیل شد موضوع 2. مشابه T6به پایان می رسد موضوع 2، موضوع 3 و موضوع 4.

بنابراین، ما در مجموع چهار سناریو داریم -

  • تک رشته همزمان
  • چند رشته ای همزمان
  • تک رشته ای ناهمزمان
  • چند رشته ای ناهمزمان

مزایای برنامه نویسی ناهمزمان

برای هر برنامه ای، دو چیز مهم است: قابلیت استفاده و عملکرد.قابلیت استفاده از این جهت مهم است که وقتی کاربر روی دکمه ای برای ذخیره برخی داده ها کلیک می کند، باید چندین کار کوچک انجام شود، مانند خواندن و پر کردن داده ها در یک شی داخلی، ایجاد اتصال به سرور SQL و ذخیره پرس و جو در آنجا و غیره. . و غیره.

زیرا سرور SQLبه عنوان مثال، به احتمال زیاد بر روی رایانه دیگری در شبکه اجرا می شود و تحت یک فرآیند متفاوت اجرا می شود، این می تواند زمان زیادی طول بکشد. و اگر برنامه بر روی یک رشته در حال اجرا باشد، صفحه نمایش دستگاه کاربر تا زمانی که همه کارها تکمیل شوند در حالت غیرفعال باقی می‌ماند، که نمونه‌ای از یک رابط کاربری بسیار بد است. به همین دلیل است که بسیاری از برنامه‌ها و چارچوب‌های جدید کاملاً به مدل ناهمزمان متکی هستند، زیرا به شما اجازه می‌دهد تا بسیاری از وظایف را در عین حفظ یک رابط پاسخگو انجام دهید.

کارایی برنامه نیز بسیار مهم است. تخمین زده می شود که هنگام اجرای یک پرس و جو، حدود 70-80٪ از زمان انتظار برای وظایف وابسته از دست می رود. بنابراین، اینجاست که برنامه نویسی ناهمزمان به کار می آید.

بنابراین، در این مقاله به این موضوع پرداختیم که برنامه نویسی همزمان و ناهمزمان چیست. تاکید ویژه بر برنامه نویسی ناهمزمان بود، زیرا اساس اکثریت قریب به اتفاق ابزارهای توسعه مدرن است. و در مطالب بعدی با مثال های واقعی با استفاده از مدل ناهمزمان آشنا می شویم.

آخرین به روز رسانی: 10/31/2015

موضوعات قبلی استفاده از ناهمزمانی با استفاده از کلمات کلیدی async و await را پوشش داده است. اما علاوه بر این مدل استفاده از تماس های ناهمزمان در سی شارپ، مدل دیگری نیز وجود دارد - استفاده از نمایندگان ناهمزمان. نمایندگان ناهمزمان قبل از معرفی async و await در سی شارپ به طور گسترده مورد استفاده قرار می گرفتند، اما اکنون async و await نوشتن کد ناهمزمان را بسیار آسان می کند. با این حال، نمایندگان ناهمزمان همچنان می توانند استفاده شوند. پس بیایید به آنها نگاه کنیم.

نمایندگان ناهمزمان به روش هایی که نمایندگان به آن اشاره می کنند اجازه می دهند که به صورت ناهمزمان فراخوانی شوند. در تاپیک مربوط به delegates گفته شد که نمایندگان را می توان با استفاده از متد Invoke یا به صورت ناهمزمان با استفاده از جفت متد BeginInvoke/EndInvoke فراخوانی کرد. بیایید به یک مثال نگاه کنیم. ابتدا، بیایید ببینیم اگر از کد همزمان معمولی در برنامه خود استفاده کنیم چه اتفاقی می افتد:

استفاده از سیستم؛ با استفاده از System.Threading. فضای نام AsyncApp ( کلاس برنامه ( نمایندگی عمومی int DisplayHandler()؛ خالی ثابت Main (رشته آرگ) ( DisplayHandler handler = DisplayHandler جدید (Display)؛ int result = handler.Invoke(); Console.WriteLine ("روش اصلی ادامه دارد") ; کنسول. برای (int i = 1; i< 10; i++) { result += i * i; } Thread.Sleep(3000); Console.WriteLine("Завершается работа метода Display...."); return result; } } }

این یک نماینده ویژه DisplayHandler ایجاد می کند که یک روش بدون پارامتر را به عنوان مرجع می گیرد که یک عدد را برمی گرداند. در این مورد، آن روش متد Display است که تا حدودی کار می کند. در این حالت چیزی شبیه خروجی زیر بدست می آوریم:

روش نمایش شروع به کار می کند روش نمایش به کار خود ادامه می دهد

به طور کلی، نمی‌توانید از یک نماینده استفاده کنید و مستقیماً متد Display را فراخوانی کنید. اما در هر صورت پس از فراخوانی آن، کار متد Main بیشتر مسدود می شود تا زمانی که اجرای متد Display به پایان برسد.

حالا بیایید مثال را با استفاده از فراخوانی نماینده ناهمزمان تغییر دهیم:

استفاده از سیستم؛ با استفاده از System.Threading. فضای نام AsyncApp ( کلاس برنامه ( نمایندگی عمومی int DisplayHandler( ؛ استاتیک void Main (string args) ( DisplayHandler handler = DisplayHandler جدید (Display); IAsyncResult resultObj = handler.BeginInvoke(null, null)؛ Console.WriteLine("روش ادامه دارد اصلی")؛ نتیجه int = handler.EndInvoke(resultObj)؛ Console.WriteLine("نتیجه (0)"، نتیجه؛ Console.ReadLine(); ) static int Display() ( Console.WriteLine("روش شروع به کار می کند نمایش..."؛ نتیجه int = 0؛ برای (int i = 1؛ i< 10; i++) { result += i * i; } Thread.Sleep(3000); Console.WriteLine("Завершается работа метода Display...."); return result; } } }

ماهیت اقدامات تقریباً بدون تغییر باقی مانده است، همان روش نمایش، فقط اکنون با استفاده از روش های BedinInvoke/EndInvoke به صورت ناهمزمان فراخوانی می شود. و اکنون می توانیم خروجی کمی متفاوت داشته باشیم:

روش نمایش شروع به کار می کند. روش نمایش به پایان می رسد

بنابراین، پس از فراخوانی متد Display از طریق عبارت handler.BeginInvoke(null, null)، متد Main تعلیق نمی شود. و اجرای متد Display از طریق Delegate DisplayHandler در رشته دیگری اتفاق می افتد. و تنها زمانی که اجرای در متد Main به خط int result = handler.EndInvoke(resultObj) برسد. آن را مسدود می کند و منتظر می ماند تا متد Display اجرا شود.

حال بیایید ویژگی های استفاده از متدهای BeginInvoke و EndInvoke و رابط IAsyncResult را بررسی کنیم.