فواید استفاده از فریمورک Seneca
- فریمورک Seneca کدنویسی برای ایجاد درخواستها، ارسال پاسخ به درخواستهای رسیده و تبدیل دادهها را که از روتینهای هر سرویسی میتوانند باشند، سادهتر میکند.
- فریمورک Seneca با معرفی ایده Actionها و Pluginها که جلوتر توضیح داده خواهند شد، روند تبدیل کامپوننتهای یک برنامه Monolithic را به نوع سرویسی، تسهیل میکند. روندی که به صورت عادی میتواند کاری طاقت فرسا باشد و نیاز به Refactoring زیادی دارد.
- سرویسهای نوشته شده با زبانهای برنامهنویسی مختلف یا فریمورکهای متفاوت میتوانند با سرویسهای نوشته شده توسط فریمورک Seneca در ارتباط و تبادل باشند.
نصب فریمورک Seneca
{ "name": "seneca-example", "dependencies": { "seneca": "0.6.5", "express": "latest" } }
var seneca = require("seneca")();
Actionها
seneca.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); seneca.add({role: "accountManagement", cmd: "register"}, function(args, respond){ });
seneca.act({role: "accountManagement", cmd: "register", username: "parham", password: "12345!"}, function(error, response){ }); seneca.act({role: "accountManagement", cmd: "login", username: "parham", password: "12345!"}, function(error, response){ });
Pluginها
function account(options){ this.add({init: "account"}, function(pluginInfo, respond){ console.log(options.message); respond(); }) this.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); this.add({role: "accountManagement", cmd: "register"}, function(args, respond){ }); } seneca.use(account, {message: "Plugin Added"});
module.exports = function(options){ this.add({init: "account"}, function(pluginInfo, respond){ console.log(options.message); respond(); }) this.add({role: "accountManagement", cmd: "login"}, function(args, respond){ }); this.add({role: "accountManagement", cmd: "register"},function(args, respond){ }); return "account"; } seneca.use("./account.js", {message: "Plugin Added"});
Serviceها
var seneca = require("seneca")(); seneca.use("./account.js", {message: "Plugin Added"}); seneca.listen({port: "9090", pin: {role: "accountManagement"}});
seneca.client({port: "9090", pin: {role: "accountManagement"}});
در اینجا هم موارد port و pin اختیاری هستند. اگر سرویسی که میخواهیم ثبت کنیم در سرور دیگری باشد، بایستی خاصیت host و با مقدار آدرس IP سرور مورد نظر در زمان ثبت، اعمال شود. سوالی که باقی میماند این است که یک سرویس چطور Actionی از یک سرویس دیگر را فراخوانی میکند؟
در بخش Actionها آمد که برای فراخوانی یک Action از متد act، از نمونهی Seneca استفاده میکنیم. رفتار Seneca به این صورت است که ابتدا بر اساس امضای Action درخواست شده، Actionهای محلی را که به سرویس جاری اضافه شدهاند، جستجو میکند. اگر تطبیقی نیافت به سراغ Actionهای ثبت شده خارجی که دارای خاصیت pin هستند، خواهد رفت و در نهایت اگر آنجا هم موردی نیافت، برای تک تک سرویسهایی که آنها را ثبت کرده، اما خاصیت pin را ندارند، درخواستی را ارسال میکند.
برای اطلاعات بیشتر به بخش مستندات فریمورک Seneca رجوع کنید.
به لطف استاندارد مدرن و هنوز فراگیر نشدهی WebAssembly ، امروزه همهی مرورگرهای مدرن میتوانند بجای اجرای جاوا اسکریپت، یک زبان bytecode استانداردِ سطح پایین و شبیه به زبان اسمبلی را اجرا کنند. استفاده از WebAssembly میتواند موجب اجرای سریعتر کد و کاهش حجم آن شود. اما مهمترین مزیت این هست که امروز میتوانیم همهی زبانهای قدرتمند، نظیر سی شارپ را به نحوی کامپایل کنیم که خروجیِ نهایی، منطبق با استاندارد webassembly باشد و به صورت native در مرورگرها، دات نت را اجرا کنیم.
کامپایل سی شارپ به WebAssembly توسط تیم Mono مایکروسافت انجام شده و عمده مشکلات فنی سر راه برداشته شدهاند. اما برای اینکه عملا بشود از دات نت در مرورگرها استفاده کرد، مایکروسافت در پی پیاده سازی پروژهی جاه طلبانهای به نام Blazor میباشد. در واقع Blazor فریم ورک Client-Side مبتنی بر دات نت خواهد بود؛ الهام گرفته از فریم ورکهای کنونی (مانند Angular و React) و رقیبی جدید برای آنها. فریم ورک Blazor هم مانند آنها حول مفهوم Component شکل گرفتهاست. کامپوننتهایی که کلاسهای سی شارپی هستند و با زبان Razor توسعه داده شدهاند.
استفاده از دات نت در مرورگرها میتواند موجب این شود که کد بیشتری را بین سرور و کلاینت بتوانیم به اشتراک بگذاریم و نیاز به دوباره کاری در هر دو سمت را نداشته باشیم. علاوه بر این توسعه دهندگان سی شارپ کمی بیشتر به مفهوم Full Stack Developer نزدیک خواهند شد.
همچنین با استفاده از WebAssembly میتوانیم به تمام کتابخانههای موجود جاوااسکریپتی هم دسترسی داشته باشیم و محدودیتی در این زمینه وجود ندارد. همچنین میتوان DOM را هم از این طریق مدیریت و دستکاری کرد.
در حال حاضر تیم AspNet عهده دار کار بر روی پروژهی Blazor شدهاست. از نوشتههای آنها چنین بر میآید که تا نهایی شدن این پروژه هنوز باید صبر کنیم.
- شناسایی منابع
- بکارگیری منابع از طریق نمایش آنها (منابع وب)
- پیامهای خودتوصیفی
- ابررسانه بعنوان قلب تپنده موقعیت برنامه
- درگیر نکردن برنامه “
- توزیع (HTTP , Feeds)
- ترکیب (Hypermedia , Mashups)
- امنیت (Open ID, SSL)
- قابلیت انتقال داده (XML,RDF)
- قابلیت نمایش داده (ATOM, JSON)
- متدهای انتقال (REST, HTTP, Bit Torrent)
- هر چیزی یک منبع است.
- هر منبعی یک تمثیل دارد.
- هر منبعی یک نام بخصوص دارد.
- انتقال موقعیت نیازمند کشف و شهود (Discovery) است.
- پروتکل شبکه پایه WOA میباشد
- اطلاعات در قالب منابع (Resources) نمایش مییابند.
- منابع توسط URIها شناخته میشوند.
- منابع از طریق HTTP اداره میشوند.
- معاهدات به صورت ضمنی در نمایش منابع میباشند.
- رابطها بطور کلی عام هستند.
- ساده سازی توسعه پذیری، مقیاس پذیری
- کاهش زمان توسعه ویژگیهای جدید
- کاهش زمان مهندسی مورد نیاز برای یکپارچه سازی
- سازنده فرصتهایی جدید برای mash-ups و دیگر داستانهای غیرقابل پیش بینی کاربری
- اما وضعیت ارتباطی کلاینتها و سرورها در WOA چگونه است ؟
- سرویسها وابسته به دیگر سرویسها هستند.
- ارتباطات از طریق HTTP صورت میگیرد.
- کلاینتها حکم منبع و سرویس دهی به دیگر کلاینتها را دارند.
- مقیاس پذیری ، توسعه پذیری == اتصالات داخی
بعنوان مثال میتوان با ترکیب تصاویر و آدرسهای مختلف دانشگاههای تهران، یک map Mashup درست کرد.
برای Photo Mashup ابزار Color Picker هم هست که امکان جستجوی تصاویر را بر اساس رنگ فراهم میکند و از سرویس اشتراک گذاری عکس Flickr استفاده میکند که در این آدرس قابل استفاده است.
معماری Mashup هم مثل معماری MVC (البته با تفاوتهای فاحش) سه لایهای است :
لایه نمایش / تعامل کاربر (همان رابط کاربری است)
تکنولوژیها : HTML/XHTML, CSS, Javascript, Asynchronous JS and Xml (Ajax).
وب سرویسها : عملکرد محصول از طریق سرویسهای API هم قابل دسترسی است
تکنولوژیها : XMLHTTPRequest, XML-RPC, JSON-RPC, SOAP, REST
داده : فراهم آوردن امکان ارسال ، مرتب سازی و دریافت داده
تکنولوژیها : XML , JSON , KML
از نظر معماری Mashup دارای 2 سبک است : الف) مبتنی بر وب – ب) مبتنی بر سرور
در ادامه با هم نمونه ای از استقرار معماری وب گرا WOA را در سازمان، بصورت شماتیک میبینیم. با هم مشاهده میکنیم با این پیاده سازی، موانع سر راه ما کاهش پیدا میکنند و سرعت یکپارچگی افزایش پیدا میکند. بدین صورت که میتوان از قدرت شبکه جهانی وب در جهت انتقال محتوای مورد نیازمان به هر جا و در هر زمانی بهره جست.
شاید برای شما سوال پیش بیاید که ما در معماری وب گرا بحث میکردیم، اصلا چرا وارد مفهوم Mashup شدیم؟
بهعبارت فنیتر چرا معماری وب گرا (WOA) برای Mashups اهمیت دارد ؟
پاسخ یک کلمه است : REST . همانطور که بالاتر نیز اشاره کردم، Mashup از REST بهره میبرد. به منظور افزایش اطلاعات در رابطه با REST باید گفت Roy Fielding آنرا بنیان نهادهاست. میخواهید او را بهتر معرفی کنم؟ وی یکی از خالقان HTTP است و مگر میتوان وب را بدون HTTP فرض کرد که مهمترین پروتکل انتقال ابر متن در جهان و پروتکل زیربنایی وب است؟!
REST به خوبی با معماری اینترنت عجین شده است! بپرسید چرا؟ چون پروتکل اصلی اینترنت HTTP است و هر دوی اینها از یک ذهن نشات گرفته و او کسی نیست جز Roy Fielding. اما باید بگویم REST یک استاندارد نیست؛ با وجود سادگی بسیار زیاد، تنها یک سبک استفاده از HTTP است.
REST همچنین از متدهای اختصاصی HTTP نظیر GET, PUT , POST , DELETE در بالای یک URL استفاده میکند تا نشان دهد چه رویدادی رخ میدهد.
در پایان گفتهها در رابطه با REST باید بگویم ATOM همان REST است. منظورم از ATOM ویرایشگر معروف متنی نیست که برای نوشتن کدهای برنامه نویسی استفاده قرار میگیرد؛ آنرا غالبا به شکل Atom مینویسند چرا که مخفف چند کلمه نیست و یک کلمه خاص است اما ATOM یک استاندارد وب به زبان XML است که برای خوراک وب بعنوان جایگزینی برای RSS استفاده میشود. ATOM را با AtomPub یا APP اشتباه نگیرید؛ چرا که APP پروتکل انتشاری است مبتنی بر پروتکل انتقال ابرمتن (HTTP) و برای به روزرسانی محتوی وب مورد استفاده قرار میگیرد.
در ادامه مباحث دررابطه با معماری وب گرا باید گفت WOA امروزه بعنوان مدل حاکم برنامههای تحت شبکه مطرح است. اما متاسفانه فروشندگان بزرگی در پشت آن حضور ندارند به همین دلیل آنچنان که باید عمومیت نیافته است. WOA همچنان میتواند بیشترین سود حاصل را از طریق شبکه فراهم کند. شاید بتوان گفت کوتاهترین مسیر برای رسیدن به چنین نتیجهای همین معماری وب گرا است.
فرمول جالبی هم برای تعریف وب ارائه شدهاست که با هم میبینیم :
HTTP + URIs = Web
ظرافت فرمول بالا به اهمیت پروتکل زیربنایی وب یعنی HTTP اشاره دارد. URI هم مجموعهای از رشتههاست که برای شناسایی یک منبع خاص تحت وب به کار میروند. در شکل زیر رابطه بین URI , URN , URL را بررسی میکنیم. URI تشکیل شدهاست از URL و URN .URL متد دسترسی به منبع را مشخص میکند، در حالیکه URN تنها مشخص کننده نام منبع میباشد و هیچگونه روشی را برای دسترسی به ما ارائه نمیدهد. بعنوان مثال یک شماره ISBN کتاب، یک نوع URN است.
ماژول ها و فضای نام(namespace)
// In the file program.fs. let x = 40
module Program let x = 40
module MyModule1 = let module1Value = 100 let module1Function x = x + 10 // MyModule2 module MyModule2 = let module2Value = 121 let module2Function x = x * (MyModule1.module1Function module2Value)
استفاده از یک ماژول در فایلهای دیگر.
گاهی اوقات نیاز به استفاده از تعاریف و توابع موجود در ماژولی داریم که در یک فایل دیگر قرار دارد. در این حالت باید به روش زیر عمل کنیم.
فرض بر این است ماژول زیر در یک فایل به نام ArithmeticFile قرار دارد.
module Arithmetic let add x y = x + y let sub x y = x - y
#1 روش اول (دقیقا مشابه روش قبل از نام ماژول استفاه میکنیم)
let result1 = Arithmetic.add 5 9
open Arithmetic let result2 = add 5 9
در #F میتوانیم بک ماژول را درون ماژول دیگر تعریف کنیم یا به عبارت دیگر میتوانیم ماژولی داشته باشیم که خود شامل چند تا ماژول دیگر باشد. مانند:
module Y = let x = 1 module Z = let z = 5
module Y = let x = 1 module Z = let z = 5
module Y = module Z = let z = 5
module Y = module Z = let z = 5
مفهوم فضای نام کاملا مشابه مفهوم فضای نام در #C است و راهی است برای کپسوله سازی کدها در برنامه. مفهوم namespace با مفهوم module کمی متفاوت است.
ساختار کلی
namespace [parent-namespaces.]identifier
#1 اگر قصد داشته باشید که از فضای نام در کدهای خود استفاده کنید باید اولین تعریف در source file برنامه تعریف namespace باشد.
#2 امکان تعریف شناسه یا تابع به صورت مستقیم در namespace وجود ندارد بلکه این تعاریف باید در ماژولها یا typeها نظیر تعریف کلاس قرار گیرند.
#3 امکان تعریف فضای نام با استفاده از تعاریف ماژول نیز وجود دارد( در ادامه به بررسی یک مثال در این زمینه میپردازیم)
تعریف namespace به صورت مستقیم:
namespace Model type Car = member this.Name = "BMW" module SetCarName = let CarName = "Pride"
module Model.Car module SetCarName = let CarName = "Pride"
همانند ماژولها امکان تعریف فضای نام تودرتو نیز وجود دارد. یک مثال در این زمینه:
namespace Outer type OuterMyClass() = member this.X(x) = x + 1 namespace Outer.Inner type InnerMyClass() = member this.Prop1 = "X"
Outer.Inner.InnerMyClass
آشنایی با مدل برنامه نویسی TAP
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
public string TestNoneAsync() { var webClient = new WebClient(); return webClient.DownloadString("http://www.google.com"); }
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
public void TestAsync() { var webClient = new WebClient(); webClient.DownloadStringAsync(new Uri("http://www.google.com")); webClient.DownloadStringCompleted += webClientDownloadStringCompleted; } void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // use e.Result }
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
using System; using System.Linq; using System.Net; using System.Windows.Forms; namespace Async02 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnGetInfo_Click(object sender, EventArgs e) { var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); txtResults.Text = headersText; }, null); } private string formatHeaders(WebHeaderCollection headers) { var headerString = headers.Keys.Cast<string>() .Select(header => string.Format("{0}:{1}", header, headers[header])); return string.Join(Environment.NewLine, headerString.ToArray()); } } }
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
private void btnGetInfo_Click(object sender, EventArgs e) { var sync = SynchronizationContext.Current; var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); sync.Post(delegate { txtResults.Text = headersText; }, null); }, null); }
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System; using System.Threading.Tasks; namespace Async01 { public class AsyncExample { public async Task DoWorkAsync(int parameter) { await Task.Delay(parameter); Console.WriteLine(parameter); } } }
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
public async Task<double> GetNumberAsync() { var generator = new Random(); await Task.Delay(generator.Next(1000)); return generator.NextDouble(); }
در ادامه برای استفاده از آن خواهیم داشت:
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
public async Task<double> GetProductOfSumAsync() { var leftOperand = GetSumAsync(); var rightOperand = GetSumAsync(); return await leftOperand * await rightOperand; }
Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
public async Task<string> DownloadAsync() { var webClient = new WebClient(); return await webClient.DownloadStringTaskAsync("http://www.google.com"); }
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
public void TestDownloadAsync() { Debug.WriteLine("Before DownloadAsync"); DownloadAsync(); Debug.WriteLine("After DownloadAsync"); }
Before DownloadAsync After DownloadAsync
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
PM> Install-Package Microsoft.Bcl.Async
آغاز سادهتر مجموعهها با کمک Collection Expressions
تا پیش از C# 12 برای آغاز یک آرایه میتوان از روش زیر استفاده کرد که در آن نوع آرایه از طریق نوع اعضای آن حدس زده میشود:
var numbers1_CS11 = new[] { 1, 2, 3 };
var numbers1_CS_11 = new int[] { 1, 2, 3 };
int[] numbers1_CS12 = [ 1, 2, 3 ];
error CS9176: There is no target type for the collection expression.
یک collection expression و یا collection literals، به مجموعهای از عناصر گفته میشود که بین دو براکت [] قرار میگیرند.
نمونهی دیگر آن کار با Spanها است که نمونه کد C# 11 آن:
Span<string> span1_CS11 = new string[] { "AC", "AL" };
Span<string> span1_CS12 = [ "AC", "AL" ];
ReadOnlySpan<string> readOnlySpan_CS12 = [ "Africa", "Asia", "Europa"];
مثال دیگر، نحوهی آغاز آرایههای چندبعدی است:
int[][] array2D_CS11 = { new int[] { 2002, 2006, 2010}, new int[] { 2014, 2018}, new int[] { 2022, 2026, 2030} };
int[][] array2D_CS12 = [ [2002, 2006, 2010], [2014, 2018], [2022, 2026, 2030] ];
و یا حتی این مورد را در مورد نحوهی آغاز Listهای پیش از C#12
List<string> list_CS11 = new List<string> { "Item 1", "Item 2" };
List<string> list_CS12 = [ "Item 1", "Item 2" ];
در کل همانطور که مشاهده میکنید، این تغییر، تغییر مثبتی است و حجم قابل ملاحظهای از کدها را کاهش داده و خواندن آنها را نیز سادهتر میکند.
یک نکته: روش ساده شدهی آغاز یک لیست با مجموعهای خالی در C# 12 به صورت زیر است:
// Before C#12 List<User> users = new List<User>(); // or var users = new List<User>(); // or List<User> user = new(); // C#12 List<User> users = [];
اضافه شدن spread operator به زبان #C
اگر پیشتر با زبان JavaScript کار کرده باشید، با spread operator هم آشنایی دارید. کار آن ساده سازی یکی کردن مجموعهها و یا افزودن سادهتر عناصری به آنها است و .. بالاخره به زبان #C هم راه پیدا کردهاست! برای مثال دو آرایهی زیر را درنظر بگیرید:
int[] numbers1_CS12 = [ 1, 2, 3 ]; int[] numbers2_CS12 = [ 4, 5, 6 ];
int[] allItems = [ ..numbers1_CS12, ..numbers2_CS12 ];
اگر در نگارشهای قبلی #C بخواهیم چنین کاری را انجام دهیم، یک روش آن به صورت زیر است:
int[] allItems_CS11 = numbers1_CS12.Concat(numbers2_CS12).ToArray();
همچنین اپراتور پخش کردن، قابلیت قرارگرفتن در کنار سایر اعضای یک آرایه را هم به سادگی و با خوانایی بیشتری به همراه دارد:
int[] join = [..a, ..b, ..c, 6, 5];
به علاوه محدودیتی در مورد نوع مجموعهی بکار گرفته شده نیز در اینجا وجود ندارد. برای نمونه در مثال زیر، یک آرایه، یک Span و یک لیست، با هم یکی شدهاند:
int[] a =[1, 2, 3]; Span<int> b = [2, 4, 5, 4, 4]; List<int> c = [4, 6, 6, 5]; List<int> join = [..a, ..b, ..c, 6, 5];
و مثالی دیگر، نحوهی سادهی تعریف لیستی از tuples است:
List<(string, int)> otherScores = [("Dave", 90), ("Bob", 80)];
(string name, int score)[] scores = [("Alice", 90), ..otherScores, ("Charlie", 70)];