استفاده از IAsyncEnumerable در جهت ایجاد وب سرویس های REST با قابلیت Stream

مقدمه :در.Net Core 3 تایپ جدیدی با عنوان های IA­syncEnumerable<T>,IAsync­Enumerator<T> در فضای نام System.Collections.Gener­icمعرفی شد.همان طور که مشخص است این تاپ های جدید کاملا با تایپ های synchronous خود هم پوشانی دارند و مفاهیم قبلی را به پیاده سازی میکنند.تایپ IAsync­Enumerable<T> متد GetAsyncEnumerator را معرفی میکند تا عملیات enumeration را به صورت async انجام دهد و در خروجی این متد تایپ IAsyncEnumerator<T> را برگشت میدهد به طوری که این تایپ disposable و دو عضو MoveNextAsync و Current را در خود دارد اولی برای رسیدن به مقدار بعدی و دومی برای دریافت مقدار فعلی استفاده می شود این در حالی است که MoveNextAsync به جای برگشت دادن یک bool یک ValueTask<bool> را برگشت می دهد.همچنین این متد مقدار CancelationToken را همانند سایر فرایند هایی که به صورت async تعریف می شوند به صورت اختیاری از ورودی دریافت میکند تا در صورت لزوم عملیات جاری را کنسل کند.از طرفی به دلیل اینکه IAsyncEnumerator اینترفیس IAsyncDisposable را پیاده سازی میکند متد DisposeAsync را نیز در اختیار دارد به طوری که به جای void یک ValueTask را برگشت میدهد.نحوه استفاده از IAsyncEnumerable : https://gist.github.com/Ershad95/db78afb43958c37142dcce538e044358 برای استفاده از این تایپ در نهایت باید از عبارت yield return استفاده کرد. تا مقدار برگشتی مشخص شده در IAsyncEnumerable که در این مثال int است برگشت داده شود.در صورت استفاده نشدن از yield خطای cannot return value from an iterator داده می شود.پیاده سازی سمت سرور :در قسمت قبل سعی بر این بود تا با این تایپ جدید آشنا شویم در این قسمت تلاش میکنیم تا با استفاده از این تایپ یک وب سرویس stream را ایجاد کنیم.ایجاد یک وب سرویس بدون خروجی IAsyncEnumerable :در مرحله اول یک وب سرویس REST را بدون استفاده از IAsyncEnumerableایجاد می کنیم تا متوجه مشکلات آن شویم و سپس در مرحله بعدی همین وب سرویس را با تایپ IAsyncEnumerable بازنویسی میکنیم. https://gist.github.com/Ershad95/7dd2920e5312263b375356734078b473 در صورت اجرای این تکه کد و فراخوانی وب سرویس موجود بعد از لود شدن کامل دیتا ، خروجی به کاربر برگشت داده می شود. این در حالی است که ممکن است کاربر فقط به بخشی از این دیتا نیاز داشته باشد برای مثال شاید صرفا به Id با مقدار ۸۰ نیاز داشته باشد اما مجبور است تا لود شدن کل دیتا صبر کند.برای رفع این مشکل وب سرویس موجود را مجدد باز نویسی میکنیم.ایجاد یک وب سرویس با خروجی IAsyncEnumerable : https://gist.github.com/Ershad95/1e42d259ed1461088dcb91f4424e54a6 این بار به محض اینکه یک دیتا ساخته شد برگشت داده می شود و منتظر تمام دیتا نیستیم این برگه برنده استفاده از IAsyncEnumerable , yield return است چرا که با ترکیب این دو میتوان وب سرویس با قابلیت stream را ایجاد کرد.از طرفی حجم payload نیز کمتر شده است چرا که هر بار صرفا یک بلاک مشخص از دیتا را به کلاینت ارسال میکنیم.تا اینجا سمت سرور را به صورت stream پیاده سازی کردیم.در قسمت بعدی سمت کلاینت را نیز پیاده سازی میکنیم تا دیتا را همانطور که سرور قسمت به قسمت ارسال میکند کلاینت نیز آن را به شکل تک قسمتی دریافت کند.پیاده سازی سمت کلاینت :در قسمت قبلی تلاش کردیم تا یک وب سرویس با قابلیت stream را پیاده سازی کنیم حال در این بخش کد کلاینت را به صورتی ایجاد میکنیم تا هر سری صرفا یک بلاک ارسال شده توسط سرور را دریافت و آن را Deserialize کند برای این کار از کتابخانه Newtonsoft.Json استفاده میکنیم. https://gist.github.com/Ershad95/7c21599d16e129f83ced744acd529315 همانطور که در کد بالا مشخص است ابتدا یک درخواست Get به آدرس وب سرویس زده و برای اینکه متجوجه شویم به انتهای لیست داده ها رسیدیم از jsonReader.TokenType != JsonToken.EndArray استفاده میکنیم با این کار در صورتی که به ] نرسیده باشیم باید عملیات خواندن از stream ادامه داشته باشد و هر سری بلاک جاری را Deserialize میکنیم و در آخر در صورتی که ایتم مورد نظر را دریافت کردیم با دستور break از حلقه دریافت بلاک ها خارج می شویم.استفاده از CancelationToken در جهت استفاده بهینه از منابع :تا اینجا به هدفی که انتظار داشتیم رسیدیم به این شکل که یک وب سرویس ایجاد کردیم تا اطلاعات را به صورت بخش بخش ارسال کند و کلاینتی ساختیم تا این اطلاعات را دریافت کند و در صورتی که اطلاعات مورد نظر را دریافت کرد به کار خواندن از وب سرویس خاتمه دهد.برای اینکه متوجه اهمیت CanclationToken شویم دو سناریو زیر را با هم بررسی میکنیم :سناریو اول - قطع کردن ارتباط توسط کلاینت :فرض کنید به هر دلیلی برای مثال خطای داخلی برنامه کلاینت و یا بسته شدن مرورگر ارتباط کلاینت با سرور قطع شود در این صورت سرور از این ماجرا خبردار نمی شود و به کار خود جهت ارسال اطلاعات ادامه می دهد .هماطور که گفته شد کلاینت به هر دلیلی از دریافت اطلاعات منصرف شده و یا به خطا خورده پس فرستادن اطلاعات هیچ کاربردی ندارد و سرور در هر مرحله ای از ارسال که باشد باید به کار خود خاتمه دهد.برای برطرف کردن مشکل این سناریو کد سمت سرور را مجدد باز نویسی میکنیم : https://gist.github.com/Ershad95/ed0f504b586075efadfcc1d69c262191 در کد بالا صرفا یک CancelationToken به ورودی متد اضافه شده و از آن در جهت اطمینان از اتصال کلاینت استفاده شده به طوری که در حلقه اصلی ارسال اطلاعات شرط cancellationToken.IsCancellationRequested چک میکند تا کاربر به دلایل مختلف از دریافت اطلاعات منصرف نشده باشد و در صورت لغو کاربر سرور به کار خود خاتمه میدهدسناریو دوم-دستیابی کلاینت به اطلاعات مورد نظر :‌کلاینت در صورتی که به اصلاعات مورد نظر از طریق وب سرویس دسترسی پیدا کرد دیگر تمایلی به ادامه خواندن از جریان داده یا stream را ندارد و از حلقه خواندن اطلاعات خارج می شود اما سرور همچنان درگیر ارسال اطلاعات است برای رفع این مشکل کد سمت کلاینت را بازنویسی میکنیم : https://gist.github.com/Ershad95/8cf7accbae02584637efb37d4fe30528 در کد بالا صرفا یک CancellationTokenSource اضافه شده و از Token آن برای ارسال وضعیت درخواست به سرور استفاده شده حال در صورتی که کلاینت به داده مورد نظر برسد با فراخوانی متد cancel() از CancellationTokenSource به سرور اطلاع می دهیم تا دیگر اطلاعاتی ارسال نکند.با استفاده از این بازنویسی سعی کردیم تا از منابع سخت افزاری و نرم افزاری استفاده بهینه تری داشته باشیم.منابع :https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8https://code-maze.com/csharp-async-enumerable-yieldGitHub Link : https://github.com/Ershad95/Stream_REST_API