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

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

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

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

چهارشنبه, ۳۰ مهر ۱۳۹۳، ۰۸:۵۸ ب.ظ

هرگز UI را منتظر نگذاریم (از آرشیو در 11 ژانویه 2014)

این مطلب در چهار قسمت ابتدا در فیسبوک منتشر شد و در اینجا هر 4 قسمت  در غالب یک مطلب واحد الحاق گردید.

 
وقتی Life-Cycle یک Windows 8 Store Application را مرور میکنیم متوجه میشویم که مجددا تفاوت های محسوسی در مدلهای مرتبط با Asynchronous Programming ایجاد شده است. این تغییر مدل تابع تغییرات بی شماریست که مخصوصا به موازات افزایش قابلیتهای پردازنده ها ، خوشبختانه یا متاسفانه ما نیز مجبور به فراگیری جزییات این تغییرات محسوس هستیم.

اما در مجموع اینک در Windows 8 حداقل یک معیار مشخصی برای انتخاب زمان درست و مناسب استفاده از این مدل در اختیار داریم. همه ما به خوبی میدانیم که معماری Metro Style در Windows 8 مقدمات کوچ به حذف Device های سنتی Keyboard و Mouse را فراهم کرده و زمینه را برای ترغیب کاربران به استفاده از صفحات قابل لمس نمایشی را در PC های امروز و آینده فراهم نموده است.
در این معیار صریحا عنوان شده است که برای عملیاتی که پیش بینی میشود بیش از 50 میلی ثانیه وقت پردازنده را اشغال خواهند کرد حتما باید از مدل Asynchronous استفاده نمود. اما بررسی سیر تغییرات این مدل، خالی از لطف نیست.
در مجموع در .Net Platform ما با سه انتخاب روبرو هستیم:

1 – مدل Asynchronous Programming Model که به اختصار APM نامیده میشود.
2- مدل Event-based Asynchronous Programing یا EAP.
3- مدل Async-Await که تا جاییکه بنده مطلع هستم هنوز نام اختصاری ندارد!.
 

مدل APM

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

در این مدل، برنامه نویس به طور معمول عملیات زمان بر (مایکروسافت بیشتر در این باره از اصطلاح Expensive استفاده میکند که بدون شک ترجمه آن هیچ کمکی به خوانندگان زبان فارسی نمیکند) را در یک جفت BeginXXX / EndXXX محصور میکند و نتیجه یا محصول عملیات مذکور را جداگانه با تخصیص یک Callback (که یک Handle به Result مورد نظر را ارائه میکند) دریافت نموده و روال طبیعی ادامه پیدا میکند.

محصول اصلی مدل Asynchronous مذکور که برنامه نویسان مشتاقانه منتظر دریافت نتیجه آن میمانند یک Interface آشنا و بسیار مشهور به نام IAsyncResult است که با دقت در نام آن متوجه میشویم که مطابق معمول مایکروسافت حتی در انتخاب نام آن بسیار هوشمندانه و ابتکاری عمل کرده است.

با مراجعه به MSDN در می یابیم که این Interface دارای تعداد چهار Properties است و به طور دقیق تر می توان چنین گفت که این Interface تعداد چهار Properties را ارائه یا Expose نموده است که به ترتیب عبارتند از :

 

AsyncState
AsyncWaitHandle
CompletedSynchronously
IsCompleted

 

اینترفیس IAsyncResult به طور معمول در کلیه کلاسهایی مثل FileStream.BeginRead که حاوی متدهایی با قابلیت اجرای Asynchronous باشند، Implement شده است.
"من شدیدا از ترجمه این اصطلاحات وحشت دارم و شاید این ترس ناشی از عدم تسلط کافی و لازم بر زبان فارسی باشد. اما معتقدم که آن مفهومی که مثلا در مواجهه با اصطلاحی نظیر Implement به ذهن برنامه نویس متبادر میشود با هیچ توضیحی قابل قیاس نیست و درک اصلی این مفهوم با استفاده از تجربه از اهمیت بیشتری برخوردار است حتی اگر متهم به رواج زبان بیگانه بشویم."
در ادامه بحث نحوه عملکرد مدل APM باید این نکته را هم اضافه کرد که یک Instance یا نمونه IAsyncResult ، همزمان با خاتمه عملیات مورد نظر ما به یک delegate ویژه به نام AsyncCallback نیز ارسال یا پاس میشود. این مفاهیم با یک مثال جنبه بسیار ملموس تری پیدا خواهند کرد:

کلاس FileStream دارای جفت متدهای BeginRead و EndRead میباشد که جهت انجام Asynchronous همان وظایفی که متد Read به صورت Synchronous انجام میداده ،(یعنی قرائت و خواندن بایت به بایت اطلاعات فایل مورد نظر) پیش بینی شده اند.
بر خلاف Read اینک در BeginRead به هیچ وجه پردازش متوقف نشده و عملیات پیش بینی شده بعد از BeginRead به همان شکل طبیعی ادامه خواهد یافت. اما CLR در پشت پرده، با ایجاد یک Thread جداگانه وظیفه خواندن Byte به Byte از فایل را آغاز میکند.
به ازای هر BeginRead یک EndRead باید تدارک دیده شود تا بتوان با کنترل کامل این عملیات نتیجه عملیات را (در این مثال بایتهای خوانده شده از فایل) دریافت نمود.
چنانچه قبلا اشاره شد نتیجه اجرای BeginRead ایجاد یک Interface از نوع IAsyncResult است که کلیه اطلاعاتی که ما برای کنترل کامل این پروسه نیاز داریم را در بر دارد.
از سوی دیگر قطعا BeginRead (و اساسا هر BeginXXX دیگری) دارای همان پارامترهایی خواهد بود که روایت Asynchronous این متد (یعنی Read) از آن استفاده میکرده است.
(البته باید توجه داشت که در صورت وجود پارامترهایی از نوع out ، امکان وجود این پارامترها در روایت Asynchronous وجود ندارد).

اما متد BeginRead دارای دو پارامتر اضافی دیگر نیز هست که اولین آنها همان دوست و آشنای قدیمی خود ما یعنی AsyncCallback و دومی پارامتریست که در بین برنامه نویسان اصطلاحا به State معروف می باشد و به طور دقیق یک User-defined Object می باشد.
اما بشنویم از قسمت دوم این ماجرا یعنی EndRead که در این لحظه میتوان گفت که اولا به طور مشخص پایان دهنده عملیات Asynchronous می باشد و ثانیا دارای مقدار بازگشتی همان متد نظیر خود یعنی Read خواهد بود.(در مثال ما یعنی EndRead تعداد بایتهای خوانده شده مقدار بازگشتی این متد می باشد)
متد EndRead علاوه بر پارامترهای متد نظیر خود، دارای یک پارامتر از نوع IAsyncResult نیز هست و فقط برنامه نویسان واقعی میدانند که این چقدر خبر خوشایند و مسرت بخشی است!!.

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

 

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Specialized;
using System.Collections;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
public class UseDelegateForAsyncCallback
{
static int requestCounter;
static ArrayList hostData = new ArrayList();
static StringCollection hostNames = new StringCollection();
static void UpdateUserInterface()
{
// Print a message to indicate that the application
// is still working on the remaining requests.
Console.WriteLine("{0} requests remaining.", requestCounter);
}
public static void Main()
{
// Create the delegate that will process the results of the
// asynchronous request.
AsyncCallback callBack = new AsyncCallback(ProcessDnsInformation);
string host;
do
{
Console.Write(" Enter the name of a host computer or <enter> to finish: ");
host = Console.ReadLine();
if (host.Length > 0)
{
// Increment the request counter in a thread safe manner.
Interlocked.Increment(ref requestCounter);
// Start the asynchronous request for DNS information.
Dns.BeginGetHostEntry(host, callBack, host);
}
} while (host.Length > 0);
// The user has entered all of the host names for lookup.
// Now wait until the threads complete.
while (requestCounter > 0)
{
UpdateUserInterface();
}
// Display the results.
for (int i = 0; i< hostNames.Count; i++)
{
object data = hostData [i];
string message = data as string;
// A SocketException was thrown.
if (message != null)
{
Console.WriteLine("Request for {0} returned message: {1}",
hostNames[i], message);
continue;
}
// Get the results.
IPHostEntry h = (IPHostEntry) data;
string[] aliases = h.Aliases;
IPAddress[] addresses = h.AddressList;
if (aliases.Length > 0)
{
Console.WriteLine("Aliases for {0}", hostNames[i]);
for (int j = 0; j < aliases.Length; j++)
{
Console.WriteLine("{0}", aliases[j]);
}
}
if (addresses.Length > 0)
{
Console.WriteLine("Addresses for {0}", hostNames[i]);
for (int k = 0; k < addresses.Length; k++)
{
Console.WriteLine("{0}",addresses[k].ToString());
}
}
}
}

// The following method is called when each asynchronous operation completes.
static void ProcessDnsInformation(IAsyncResult result)
{
string hostName = (string) result.AsyncState;
hostNames.Add(hostName);
try
{
// Get the results.
IPHostEntry host = Dns.EndGetHostEntry(result);
hostData.Add(host);
}
// Store the exception message.
catch (SocketException e)
{
hostData.Add(e.Message);
}
finally
{
// Decrement the request counter in a thread-safe manner.
Interlocked.Decrement(ref requestCounter);
}
}
}
}


مدل EAP

از این مدل به عنوان برادر مدل APM یاد میشود و همانطور که از نام آن مشخص میباشد، Event Handling در آن نقش کلیدی ایفا میکند و نتیجه عملیات Asynchronous در قالب یک Callback همواره با استفاده از Event Handling قابل دریافت است.

یکی از نشانه های بارز آن زمانیست که با وضعیتی مانند زیر روبرو میشویم:
DoSomethingComplete += ExecutionEndFunction();

برنامه هایی که چندین وظیفه (Tasks) را به صورت همزمان (Simultaneously) بدون منتظر گذشتن UI انجام میدهند، اغلب نیازمند به استفاده از تکنیک Multiple Threads هستند .
هر Thread (که من عمدا از ترجمه آن به معادل "رشته" پرهیز میکنم) در واقع کوچکترین واحد اجرای عملیات (Tasks) در پردازنده محسوب میشود که معمولا در یک Process اختصاص یافته از سوی سیستم عامل وظایف محول شده را به انجام می رساند. از سوی دیگر یک Process میتواند شامل چندین Thread جداگانه باشد که این Threadها در کنار هم و در قالب یک نظم و منطق مشخصی وظایف مختلف منتسب به Process مذکور را انجام میدهند.
درست نظیر همه مباحث دیگر تکنیکی برنامه نویسی، به نظر میرسد در اینجا نیز زمینه برای انحراف از موضوع اصلی فراهم شده است.
اما بحث Thread ها خود بحث بسیار فنی، مفصل و جدا گانه ایست و من دوباره صرفا به یک مثال بسیار ساده و مختصر در اینجا اکتفا خواهم نمود.

 

using System;
using System.Threading;
class ThreadTest
{
static void Main()
{
Thread t = new Thread (WriteY);
t.Start();
// اجرای هم زمان یک وظیفه در Thread اصلی برنامه
for (int i = 0; i < 1000; i++) Console.Write ("x");
}
static void WriteY()
{
for (int i = 0; i < 1000; i++) Console.Write ("y");
}
}


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

 

xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyy
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

ابتدا Thread اصلی برنامه یک Thread دیگر به نام t ایجاد میکند که Constractor کلاس Thread با دریافت یک delegate انتظار دارد که آدرس متد مورد نظر ما جهت انجام وظیفه ای که در Thread جدید قرار است انجام شود را دریافت کند. متد WriteY از نظر Signature (نوع مقدار بازگشتی که در اینجا void و نوع و تعداد پارامترها که در اینجا فاقد پارامتر است) کاملا با متد جایگزین در delegate منطبق است . در ادامه متد Start کلاس Thread فراخوانی شده که فرمان اجرای Thread مذکور را صادر میکند. همزمان و در ادامه، برنامه مشغول انجام وظیفه دیگری (ارسال x به خروجی) می باشد . سیستم عامل در سیستم هایی که تعداد Core پردازنده یکی باشد به طور تقریب هر بار Time Slice یا فرصت زمانی به میزان 20 میلی ثانیه در اختیار هر Thread قرار میدهد و نتیجه همان است که ملاحظه نمودید در سیستم های چند هسته ای (Multicores) وضعیت به گونه نسبتا متفاوتیست که جزییات کیفیت انجام آن، خارج از حوصله بحث ماست.

از آنجاییکه ایجاد، مدیریت و کنترل Thread ها نیاز به تجربه قابل توجهی دارد خوشبختانه یک کامپوننت ویژه که اتفاقا مصداق بارزی برای بحث مدل EAP نیز میباشد برای انجام این کار به نحو بسیار ساده تری پیش بینی شده است. نام این کامپوننت BackgroundWorker میباشد که برنامه نویسان با تجربه حتما از آن بارها در سناریوهای مختلف استفاده نموده اند.
به نظر میرسد در مدل EAP طراحان این مدل به شدت دنبال این هدف بوده اند که برنامه نویسان ضمن بهره گیری از مزایای استفاده از برنامه های Multithreaded ، از بسیاری از جنبه های دشوار و غامض آن دور مانده و از آن جهت ارتقای سطح کیفی در برنامه های خود به راحتی استفاده نمایند.

طراحان این مدل ، استفاده از آن را در موارد زیر توصیه میکنند:

1- اجرای پشت پرده یا در زمینه وظایف زمان بر (Time-consuming tasks) نظیر دانلود و یا عملیات سنگین مرتبط با Database بدون ایجاد وقفه (Interrupt) در وظایف جاری برنامه.

2- اجرای چند عمل همزمان به نحوی که در هنگام اتمام هر یک از آنها به طور جداگانه، سیستم عامل برنامه نویس را با ارائه اطلاعات مفید، از این موضوع آگاه (Notification) نماید.

3- مدیریت اتوماتیک هر Thread به عنوان کوچکترین واحد اجرای یک وظیفه (Task) به نحوی که در صورت کمبود منابع (Resources) (منظور از منابع به طور مشخص حافظه Ram و جلب توجه زمان پردازنده می باشد) به طور موثر و غیر آزار دهنده ای منتظر بمانند.

4- برقراری ارتباط پویا بین عملیات و وظایف (تعیین شده برای اجرای هم زمان) در انتظار (Pending asynchronous operations) با بهره گیری از مکانیسم آشنای Eventها و delegateها.

از نشانه های آشکار کلاسی که بر اساس مدل EAP طراحی شده باشد وجود متد یا متدهایی است که در نامگذاری آنها از الگوی مشترک MethodNameAsync استفاده شده باشد.در برخی از مواقع (و نه در همیشه) این متدها درست نظیر مدل APM دارای معادل Synchronous نیز میباشند که همان عملیات منسب را در Thread جاری برنامه اجرا میکند و بدیهیست که از دیدگاه مقایسه Performance و کارآیی با نظیر خود اصلا قابل مقایسه نیستند. این کلاسها همچنین میتواند شامل Eventهایی با الگوی نام مشترک MethodNameCompleted و MethodNameCancel (یا به سادگی CancelAsync) باشند.

UI و کنترل آشنای PictureBox یکی از مشهورترین کامپوننتهایی است که از مدل EAP حمایت کرده و از آن استفاده میکند. در یک انتخاب ناشیانه، ما قادر هستیم با بهره گیری از متد Load این کامپوننت یک Image خاص را جهت نمایش در این قاب به صورت Synchronous دانلود نماییم. اما اگر حجم تصویر نسبتا زیاد (مثلا بیشتر از 200 کیلو بایت) و یا کیفیت ارتباط شبکه ضعیف باشد، قطعا در خوش بینانه ترین حالت ممکن، کل برنامه به مدت زمان قابل توجه (و البته آزار دهنده ای) متوقف و یا در بدترین حالت به طور کامل Hang خواهد کرد. و در حالت اول این توقف درست تا لحظه اتمام عملیات متد Load ادامه خواهد داشت.

اما انتخاب بهتر برای عدم توقف برنامه و به تعبیری پاسخگو بودن کلیه اجزای رابط کاربر (Responsive UI) در هنگام Loadو یا دانلود تصویر ، فراخوانی LoadAsync و به موازات آن توجه و Handle کردن رویداد LoadCompleted می باشد که به شکل بسیار بسیار محسوسی کیفیت و کارآیی (و به تعبیری ارزش فنی کار) را افزایش خواهد داد.

بعد از فراخوانی LoadAsync برنامه بر خلاف وضعیت قبل بدون هیچگونه توقفی به انجام روال عادی خود ادامه خواهد داد ولی پنهان از دید و نظر برنامه نویس (در Background) یک Thread اختصاصی برای ادامه موثر عملیات Load یا دانلود تصویر آغاز میشود و نیازی نیست که ما نگران جزییات نگران کننده این عمل (مثل زمان انتظار و یا حجم بیش از اندازه تصویر) باشیم. چرا که درست در زمان مناسب یعنی اتمام عملیات ، این خبر مسرت بخش در قالب یک Event به ما اطلاع داده میشود و مهمتر از همه آنکه این Event شامل اطلاعات بسیار ارزشمندی در قالب یک پارامتر از نوع AsyncCompletedEventArgs میباشد که بخشی از آن اطلاعات تصویر Load شده خواهد بود که به سادگی میتوان از آن برای نمایش تصویر در PictureBox استفاده نمود. حتی در صورت موفقیت آمیز نبودن عملیات از همین پارامتر میتوان برای ارائه یک گزارش به کاربر و اعلام نوع و کیفیت خطای ایجاد شده استفاده نمود.

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

در انتهای بررسی اجمالی این مدل باید به این موضوع نیز اشاره کنیم که در همین سناریوی Load تصویر، حداقل انتظار یک برنامه نویس با تجربه این است که از زمان تخمینی کل عملیات و مقدار زمان سپری شده یا باقیمانده مطلع باشد تا مثلا با تدارک دیدن یک Progress Bar این اطلاعات به بهترین شکل ممکن در اختیار کاربر قرار بگیرد. متاسفانه این ویژگی نه به صورت اجباری بلکه به صورت اختیاری در این مدل پیش بینی شده است و اتفاقا کامپوننت BackgroundWorker دارای این ویژگی بسیار مفید میباشد. در یک الگوی کلی چنین اطلاعاتی در قالب یک Event با الگوی نام ProgressChanged یا MethodNameProgressChanged تدارک دیده میشود که مطابق معمول اطلاعات مرتبط با پیشرفت عملیات در پارامتری از نوع ProgressChangedEventArgs به داخل Event هدایت میشود. در این وضعیت معمولا ما با بررسی ProgressChangedEventArgs.ProgressPercentage از میزان، مقدار یا درصد کار انجام شده مطلع میشویم که این مقدار در بازه اعداد بین صفر تا 100 قرار دارد. نیازی به توضیح بیشتر نیست که امکان استفاده از این مقدار در یک ProgressBar با تغییر مقدار Value میسر خواهد بود. در صورتیکه مایل باشیم میزان پیشرفت درصد کار انجام شده چندین عمل Asynchronous مختلف را تنها با یک Event کنترل نماییم استفاده از ProgressChangedEventArgs.UserState به عنوان یکی از بهترین راه حل ها توصیه شده است.

به عنوان یک مثال ساده در مدل EAP میتوان به کاربرد ساده ای از کلاس WeClient اشاره کرد.
ابتدا اجزای الگوهای مشترک را در Member های این کلاس ملاحظه کنید:

 

public byte[] DownloadData (Uri address);
public void DownloadDataAsync (Uri address);
public void DownloadDataAsync (Uri address, object userToken);
public event DownloadDataCompletedEventHandler DownloadDataCompleted;
public void CancelAsync (object userState);
public bool IsBusy { get; }

انجام یک دانلود به صورت Asynchronous توسط این کلاس در ساده ترین شکل خود میتواند به صورت زیر باشد:
var wc = new WebClient();
wc.DownloadStringCompleted += (sender, args) =>
{
if (args.Cancelled)
Console.WriteLine ("Canceled");
else if (args.Error != null)
Console.WriteLine ("Exception: " + args.Error.Message);
else
{
Console.WriteLine (args.Result.Length + " chars were downloaded");
}
};
wc.DownloadStringAsync (new Uri ("http://www.SomeWebsite.com"));

 

در خاتمه مایلم اشاره کنم که قطعا برنامه نویسانی که تجربه کار با Silverlight و WCF را دارند، بدون شک بارها از مدل EAP استفاده نموده اند و این مدل تا قبل از ظهور Windows 8 Store Application و مخصوصا در تکنولوژی های مدرن که دغدغه ایجاد UI های Touch Enabled یا قابل لمس را دارند، توصیه شده و در سطح وسیعی استفاده شده است.
اما برای سومین بار، چنانچه در قسمت سوم این مطلب به آن اشاره خواهم کرد، رویکرد و استراتژی Microsoft در ارائه مدلهای برنامه های Asynchronous تغییر کرد و مدل Async-await تنها مدل قابل استفاده در مدرنترین تکنولوژی حال حاضر Microsoft یعنی Windows 8 Store Application میباشد.

 

 مدل Async-await
آخرین مدل توصیه شده Microsoft برای پیاده سازی سناریوهای مرتبط با Asynchronous Programming صرفنظر از پیشرفتهای قابل توجه در ویژگیهای فنی این مدل، یک نمونه عالی از تشریک مساعی CLR با قابلیتهای ارزنده زبان C# به عنوان یک زبان مستقل برای طراحی و تدوین برنامه هایی در کاربردهای عمومی (General Purpose Applications) نیز میباشد.
کلاس Task که در این مدل نقش بسیار مهمی ایفا میکند ابتدا در .Net Framework 4.0 معرفی گردید و بخشی از اهداف بسیار بزرگتری بود که بخشهای وسیعی از این اهداف بعدها در Framework 4.5 محقق گردید.
اگر به لیست تغییرات C# 5.0 نسبت به 4.0 توجه کنیم، ابتدا در ظاهر و در مقایسه با تغیرات قبلی این لیست کمی مأیوس کننده به نظر می رسد. در واقع در صدر این لیست نه چندان مفصل نام مدل Async-await دیده خواهد شد. با مطالعه بیشتر و بررسی دقیق تر این ویژگی، می توان قاطعانه عنوان کرد که تغییرات این نسخه اخیر بیشتر تغییراتی از دیگاه کیفی هستند و نباید کمیت ناچیز لیست این تغییرات، شبهه ای در علاقمندان و برنامه نویسان این زبان قدرتمند ایجاد نماید.
کاربرد کلاس Task در Framework 4.0 صرفا محدود به استفاده در زمینه های Parallel Programming بود. اما در ادامه به طور تخصصی برای برطرف نمودن برخی از نقاط ضعف استفاده از Thread ها طراحی شد که دو نمونه از بارزترین موارد قابل ذکر به شرح زیر میباشند:
1) در کلاس Task دیدگاه Compositional بودن Task بسیار مورد توجه بوده است و معنای دقیق این گفته این است که بر خلاف Thread حالا در این کلاس امکان ارتباط زنجیر وار (Chained) چند Task با استفاده از منطق مشخص امکان پذیر میباشد. بعلاوه در مورد Thread ابدا امکان Restart کردن یک Thread وجود ندارد و پس از استفاده از متد Join که به منزله اجبار به انتظار جهت خاتمه Thread مذکور است، Thread اجرا شده به هیچ وجه امکان درخواست دریافت Time Slice (درخواست برای توجه CPU و اختصاص زمان) را نداشته و برای همیشه خاتمه یافته است.
2) با اینکه در Threadها امکان ورود پارامتر میسر می باشد اما دریافت خروجی از Threadها بسیار دشوار و با استفاده از روشهای عادی امکان پذیر نیست. یکی از روشهای رایج برای انجام این کار استفاده از داده های مشترک (Shared Data) و به طور مشخص فیلدهای Static میباشد که بالقوه امکان بروز خطاهای متعددی را افزایش می دهد. اما کلاس Task برای جبران این نقص تدارک ویژه ای دیده است که در ادامه به اجمال آن را بررسی خواهیم کرد.
در یک توصیف مختصر روند کار با Task از این قرار است که ابتدا در خلال نمونه سازی (Instantiate) این کلاس، عملیات مورد نظر خودمان را (که در ترمینولوژی این مدل، این عملیات به سادگی Job نامیده میشوند) در قالب یک بلاک Action از طریق یکی از 8 سازنده (Constructor) کلاس به کلاس معرفی نموده و متد Start را جهت درخواست کنترل از سوی OS و اختصاص یک زمان بندی مشخص (Specified Scheduling) فراخوانی میکنیم. در یک راهکار جداکانه می توانیم یک نمونه از Object این کلاس را به متدهایی که پارامتری از نوع Task دریافت میکنند منتقل نماییم. که در ادامه مثالهایی در این زمینه ارائه شده است.
این موارد تنها دو نمونه از روشهایی هستند که به طور معمول در سناریوهایی که نیاز به استفاده از Task وجود دارد مورد استفاده قرار میگیرند. واقعیت این است که من از تنوع روشهای موجود در ایجاد Task و روشهای اختصاصی سازی (Customize) نمودن عملیات آن بسیار شگفت زده گشته و البته در برخی از موارد با تناقضهایی هم در نحوه استفاده از آن روبرو شدم که به برخی از مهمترین آنها اشاره خواهم کرد. هر چند متاسفانه تا این لحظه هیچ توضیح منطقی برای آن ندارم.
برای روشن شدن همه این موارد، در ادامه از مثالهای ساده ای استفاده خواهم کرد ، هر چند که من هنوز مخصوصا در Windows 8 Store Application در چندین سناریوی مختلف به دنبال کسب تجربه و تسلط بیشتر برای این مدل جدید میباشم . این تذکر بیشتر به این دلیل است که هیچ ادعایی بر بی نقص بودن این روشها وجود ندارد و اتفاقا انتظار دارم که همکاران محترم حتما در صورت وجود نظرات و یا روشهای بهتر بنده را از این نظریات ارزشمند آگاه نمایند.
مرور توصیف کلاس Task و مخصوصا متد ها و فیلدهای آن که در محدوده نام System.Threading.Tasks و در اسمبلی mscorlib قرار دارد، می تواند ذهنیت بسیار مناسبی را در خوانندگان، از قابلیت های این کلاس ایجاد کند:


public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable
{
/* 8 overloaded */
public Task(Action action) { }
/* 21 overloaded */
public ConfiguredTaskAwaitable ConfigureAwait(
bool continueOnCapturedContext) { }
/* 4 overloaded */
public static Task Delay(int millisecondsDelay) { }
public void Dispose() { }
public static Task<TResult> FromResult<TResult>
(TResult result) { }
public TaskAwaiter GetAwaiter() { }
/* 8 overloaded */
public static Task Run(Action action) { }
/* 2 overloaded */
public void RunSynchronously() { }
/* 2 overloaded */
public void Start() { }
/* 5 overloaded */
public void Wait() { }
/* 5 overloaded */
public static void WaitAll(params Task[] tasks) { }
/* 5 overloaded */
public static int WaitAny(params Task[] tasks) { }
/* 4 overloaded */
public static Task WhenAll(IEnumerable<Task> tasks) { }
/* 4 overloaded */
public static Task<Task> WhenAny(IEnumerable<Task> tasks) { }
public static YieldAwaitable Yield() { }
public object AsyncState { }
public TaskCreationOptions CreationOptions { }
public static int? CurrentId { }
public AggregateException Exception { }
public static TaskFactory Factory { }
public int Id { }
public bool IsCanceled { }
public bool IsCompleted { }
public bool IsFaulted { }
public TaskStatus Status { }
}


ساده ترین شکل استفاده از Task ، بدون نیاز به نمونه سازی (Instantiate) فراخوانی متد استاتیک Run و استفاده از delegate و یا بهره گیری از تکنیک Lambda مییاشد که همانگونه که همکاران محترم مستحضر می باشند این دو تکنیک اغلب هر کدام به عنوان یک روش جایگزین (Alternative) در بسیاری از کاربردها به جای هم مورد استفاده قرار میگیرند . در مورد مزیت و یا ضعف های هر یک از این دو روش نسبت به یکدیگر باید اطمینان داشت که اساسا هر دو روش از دیدگاه Performance، کاملا یکسان بوده و معیار انتخاب برنامه نویسان در این مورد بیشتر وضوح و خوانایی کد و کاملا وابسته به سلیقه شخصی است.
به این جهت با کسب اجازه از محضر شما بزرگواران من هر دو روش را در دو مثال جداگانه و به شرح زیر ذکر خواهم نمود.

متد استاتیک Run خود دارای 8 نسخه Overload  شده می باشد
روش اول استفاده از تکنیک Lambda


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AsyncUsingTask
{
class Program
{
static void Main()
{
Task displayTask = Task.Run(() => Console.WriteLine("async in C# 5.0")); /* ادامه تا زمانیکه عملیات منتسب خاتمه پیدا کند*/
while (true)
{
/* بررسی شرط خاتمه عملیات */
if (displayTask2.IsCompleted)
{
Console.WriteLine("Task completed!");
break;
}
}
Console.ReadLine();
Console.WriteLine("Press <Enter> to Exit.");
}
}
}


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


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AsyncUsingTask
{
class Program
{
static void Main()
{
Task displayTask = Task.Run(delegate()
{
Console.WriteLine("async in C# 5.0 delegate");
});
/* ادامه تا زمانیکه عملیات منتسب خاتمه پیدا کند*/
while (true)
{
/* بررسی شرط خاتمه عملیات */
if (displayTask2.IsCompleted)
{
Console.WriteLine("Task completed!");
break;
}
}
Console.ReadLine();
Console.WriteLine("Press <Enter> to Exit.");
}

}
}

 

و در نهایت خروجی برنامه عبارت است از:

 

async in C# 5.0
Task completed!
Press <Enter> to exit.

 

چنانچه قبلا نیز اشاره شد و قابل پیش بینی نیز هست با استفاده از سازنده کلاس Task نیز امکان ایجاد یک Task وجود دارد.


using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace AsyncUsingTask
{
class Program
{
static void Main()
{
Task displayTask = new Task(() => Console.WriteLine("async in C# 5.0"));
displayTask.Start();
displayTask.Wait();
Console.ReadLine();
}
}
}

 

یکی از موارد تناقضی که قبلا از آن یاد کردم در بحث استفاده از سایر فرم های Overload شده سازنده میباشد. در حقیقت بر اساس مستندات موجود در کلاس Task این انتظار وجود دارد که یکی از سازنده ها دو پارامتر ی:ی از نوع Action و دیگری از نوع یک Enumeration به نام TaskCreationOption دریافت نماید که متاسفانه هنگام استفاده از آ« من با خطای Syntax در Visual Studio 2012 روبرو شدم و در حال حاضر هم هیچ توضیحی برای آن ندارم.
اما همانطور که ملاحظه میکنید متد Start هم که بر خلاف Run یک Instance Method می باشد نیز قادر به اجرای یک نمونه از قبل ایجاد شده از کلاس Task میباشد. تفاوت اصلی و بسیار قابل توجه متد Start مثلا با متد Run صرفنظر از تفاوت نوع تعلق به کلاس، در این است که با استفاده از این متد در حقیقت Task مذکور با استفاده از کلاس TaskScheduler در یک جدول زمان بندی قابل توجه با انتخابهای متعدد قرار میگیرد که در ادامه آن را بررسی خواهیم کرد اما قبل از آن من مایل هستم که دو مورد از ویژگیهای برجسته کلاس Task را که قبلا به یکی از آنها اشاره کردم، در قالب یک مثال ساده بیان کنم. ابتدا لطفا به کد مثال توجه نمایید:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace AsyncUsingTask
{
class Program
{
static void Main()
{
Task<string> displayTask = new Task<string>(() => "async in C# 5.0");
displayTask.Start();
Console.WriteLine("The result from the Task {0}", displayTask.Result);
Console.ReadLine();
}
}
}


اولین نکته قابل ذکر در این مثال استفاده از تکنیک Generic در کلاس Task میباشد که لازم به ذکر نیست که همین ویژگی به تنهایی قابلیت Flexiblily و انعطاف بسیار مهمی را به این کلاس اضافه نموده است. نکته دوم قابلیت استفاده از مقادیر بازگشتی آن هم به ساده ترین روش ممکن بعد از اجرای وظیفه یا Job منتسب به Task می باشد که آن را نخصوصا نسبت به Thread متمایز میسازد.
به هر حال بدون تردید در هنگام استفاده از کلاس Task و مخصوصا با توجه به وضعیت های متعددی که در هنگام کار با این کلاس ممکن است با آن روبرو بشویم، نیاز مبرمی به وجود ی: مکانیسم مشخص جهت آگاهی از ای« وضعیت ها ضروری به نظر میرسد.
برنامه نویسی که کاربرد مشخصی از کلاس Task را در یک سناریوی دلخواه پیاده سازی میکند نیاز دارد تا حداقل مطمئن باشد که آیا Task آغاز شده خاتمه یافته، احیانا Cancel شده و یا احتمالا با خطایی روبرو شده است. خبر خوب در این زمینه این است که یک Enumeration ویژه به نام TaskStatus اساسا برای پیگیری و اطلاع از همین موضوع پیش بینی شده است که به صورت زیر در System.Threading.Tasks تعریف شده است:


public enum TaskStatus
{
Canceled = 6,
Created = 0,
Faulted = 7,
RanToCompletion = 5,
Running = 3,
WaitingForActivation = 1,
WaitingForChildrenToComplete = 4,
WaitingToRun = 2
}


برای اطلاع از این وضعیتها فقط کافیست که مقدار Status را که یک Data Member با دسترسی public است بررسی نماییم. مقدار Created توسط CLR و درست در هنگام نمونه سازی کلاس Task (و یا چنانچه پیشتر ملاحظه کردیم توسط متد استاتیک Run) در Status قرار میگیرد. در ادامه درست همزمان با شروع Task مقدار Status به WaitingToRun تنظیم خواهد شد. اجرای Task با مقدار ناچیزی تاخیر آغاز میشود که نحوه کنترل آن و چگونگی انجام آن در ادامه و در مبحث TaskSceduler بیان خواهد شد.
اما به هر حال بعد از اجرا مقدار Status به Running تنظیم شده و در صورت توقف احتمالی Task مقدار Status به WaitingForChildrenToComplete تنظیم میگردد. در ادامه CLR مقدار Status را بعد از خاتمه یک Task یه یکی از سه مقدار RunToCompletion یا Canceled یا Faulted تغییر خواهد داد که تصور میکنم خود مقادیر به اندازه کافی گویا هستند .
مقدار RunToCompleted در Status خاتمه موفقیت آمیز یک Task را به ما اطلاع میدهد و Canceled بیان کننده این مطلب است که اجرای Task مذکور به دلایلی (که در جای خود حتما به آنها خواهیم پرداخت) بدون حصول به نتیجه، خاتمه یافته است. و مقدار Faulted حاوی اخبار مایوس کننده ای مبنی بر بروز یک خطا در حین اجرای وظایف منتسب به Task ، می باشد.
نظر به اهمیت این سه وضعیت این کلاس دارای سه Accessor با دسترسی public متناظر با این سه وضعیت میباشد که برای اطلاع از هر یک از این سه وضعیت می توان به جای بررسی Status از آنها استفاده نمود.
یکی دیگر از قابلیت های بسیار مهم در کلاس Task اصطلاحا Continuations نام دارد که قبل از توضیح آن لازم است به یک مثال ساده اما مهم در این باره توجه نمایید:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace AsyncUsingTask
{
class Program
{

static void Main(string[] args)
{
ShowEvenNumbers();
Console.WriteLine("Waiting.....");
for (int i = 0; i <= Int16.MaxValue * 100; ++i) ;
Console.WriteLine("End.");
Console.ReadLine();
}
static void ShowEvenNumbers()
{
Func<int> MFunc = MyMethod;
//Task<int> evenNumbersTask = Task.Run(() => Enumerable.Range(1,50000).Count(item => item%2==0));
Task<int> evenNumbersTask = Task.Run(MFunc);
TaskAwaiter<int> awaiterEven = evenNumbersTask.GetAwaiter();
awaiterEven.OnCompleted(() =>
{
Console.WriteLine("Complete, Total no of even : {0}",
awaiterEven.GetResult());
});
Console.WriteLine("Schedule to complete...");
}
static int MyMethod()
{
return Enumerable.Range(1, 50000).Count(item => item % 2 == 0);
}
}
}

 

 در ترمینولوژی Asynchronous Programming اصطلاح Continuation code block اشاره به بلاک مشخصی از دستورات است که بلافاصله پس از اتمام عملیات، اجرا می گردد.
در مدل Async-await این کار با استفاده از سه تکنیک به شرح زیر قابل انجام است:


1- استفاده از متد OnCompleted
2- استفاده از متد ContinueWith
3- استفاده از Keyword یا دستور await


آخرین مثالی که در پایان قسمت سوم ذکر شده بود، یک پیاده سازی ساده از روش اول میباشد که در متد ShowEvenNumbers و در سطر اول این متد ، هدف فقط یادآوری این نکته بوده است که امکان استفاده از تکنیک delegateهای Generic میسر است. متد استاتیک MyMethod دقیقا چنین نقشی را عهده دار بوده و در این مثال ساده تعداد اعداد زوج بین 1 تا 50000 محاسبه شده است.
در سطر دوم متد ShowEvenNumbert دقیقا همین عمل با استفاده از تکنیک Lambda انجام شده و البته Comment شده است.
از دیدگاه تحلیل کد، متد استاتیک Range متعلق به کلاس Enuerable به سادگی یک Colletion (غیر Generic) از محدوده اعداد بین 1 تا 50000 ایجاد میکند. ناگفته پیداست که در این هنگام و در سناریو های مورد نیاز به سادگی میتوان این Collection را به یک List از نوع Generic تبدیل نمود(مثلا با استفاده از متد ToList یا انتقال خروجی Range مستقیما به سازنده یک لیست Generic نظیر List<int> ).
با علم بر Collection بودن خروجی متد Range بنابراین امکان استفاده از متد Count که انتظار دریافت یک Predict را دارد، خیلی دور از ذهن نیست.
اما در سطر سوم متد ShowEvenNumber این Task متفاوت ما مجددا این بار با استفاده از متد استاتیک Run ایجاد شده است.
تاکید من بر کلمه متفاوت از آن جهت است که بوضوح ردپای قابلیت Generic کلاس Task را مشاهده میکنیم و همین ویژگی (منظور قابلیت ورود یک نوع یا Type به داخل Task و انتظار دریافت خروجی) آن را نسبت به سایر روشهای Asynchronous Programming که تا به حال بررسی کردیم متمایز میسازد.
اما در ادامه برای دستیابی به یک Continuation code block که امکان اجرای بلاک مشخصی از کد را در پایان عملیات Task امکان پذیر نماید نیاز به یک TaskAwaiter وجود دارد که مطابق مثال، به سادگی با فراخوانی متد GetAwaiter ، این نیاز برطرف خواهد شد.
به نظرم توضیح این نکته در اینجا ضروریست که TaskAwaiter در واقع یک Struct با قابلیتهای Generic است (این نکته از آن جهت حائز اهمیت است که به طور معمول ما باید استراتژی های جداگانه، مشخص و تعیین شده ای در هنگام کار با یک Value Type و یا Reference Type داشته باشیم) و در System.Runtime.CompilerServices تعریف شده و جالب است بدانیم که به دلیل وظیفه بسیار حساسی که به عهده دارد اینترفیس های ICriticalNotifyCompletion و INotifyComletion را Implement نموده است:


public struct TaskAwaiter<TResult> :
ICriticalNotifyCompletion, INotifyCompletion
{
public bool IsCompleted {}
public void OnCompleted(Action continuation) {}
public void UnsafeOnCompleted(Action continuation) {}
public TResult GetResult() {}
}

 

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

این کار با تکنیک Lambda و استفاده از متد OnCompleted انجام شده و در داخل بلاک تعیین شده یک فراخوانی ساده متد GetResult چنانچه از نام آن نیز مشخص می باشد، حاوی نتیجه عملیات Task مورد نظر ما خواهد بود.
در یک جمع بندی در این مثال سه نکته مورد توجه بوده است . اول امکان Generic بودن Taskها . دوم امکان تعیین یک Continuation code block برای دریافت خروجی عملیات منتسب به Task و در نهایت سوم امکان دریافت خروجی از طریق متد GetResult.

چنانچه در ابتدای همین قسمت اشاره شد سه روش برای دستیابی به Continuation code block وجود دارد که من جهت پرهیز از اطاله کلام فقط به اشاره کوتاهی به روش دوم یعنی استفاده از متد ContinueWith بسنده خواهم نمود. بنابراین فقط با یک مثال کوتاه به استفاده از آن در متد ShowEvenNumbers خود اشاره کرده و تفاوت آن را ذکر خواهم کرد:


evenNumbersTask.ContinueWith(task =>
{
Console.WriteLine("Complete, Total no of even : {0}",
task.Result);
});

 

این متد دارای 19 فرم Overload شده است!!! که در این مثال صرفا با دریافت یک پارامتر delegate (Func<TResult>) و استفاده از Property یا خاصیتی به نام Result در همین پارامتر، امکان گزارش خروجی عملیات Task را فراهم می نماید.
اما تا اینجا همه انرژی ما در تشریح این مدل صرف بررسی اجمالی Task شد و هنوز ردپایی از async-awiat مخصوصا به ترتیبی که در مستندات C# 5.0 آمده است وجود ندارد.
در یک جمع بندی مختصر، آنچه تا این لحظه بررسی شد محوریت نقش کلاس Task و استفاده از یک Continuation code block برای دریافت خروجی بود.
اما خبر خوب در اینجا این است که اساسا هدف از async-wait ایجاد روال ها ویا متدهای Asynchronous بدون نیاز به تعیین یک Continuation code block جداگانه میباشد. آنچه مصرانه توسط Microsoft در هنگام معرفی این مدلها توصیه شده است ، لزوم استفاده در سناریوهاییست که نیاز به برنامه های I/O-bound یا Compute-bound وجود دارد.
اصطلاح I/O-bound به عملیاتی اطلاق میشود که بیشتر زمان خود را صرف انتظار (waiting) برای انجام کاری مشخص میکنند. از نمونه های بارز آن میتوان به دانلود یک Web Page و یا حتی انتظار ناشی از فراخوانی ReadLine اشاره کرد. به عبارت ساده تر توقف ناشی از انتظار دریافت از ورودی و یا ارسال به خروجی در محدوده عملیات I/O-bound می باشند. در مقابل Compute-bound که گاهی CPU-bound نیز نامیده میشود، به عملیاتی اطلاق میگردد که انتظار، صرفا جهت درگیری پردازنده برای محاسبات دشوار و زمان بر میباشد. به عنوان مثال میتوان به مرتب سازی و Sort تعداد بیشماریاز رکوردها اشاره نمود.
توجه به این ویژگی منحصر به فرد که وظیفه ایجاد Continuation code block در هنگام استفاده از کلیدواژه های (از به کار بردن این معادل برای Keyword واقعا متنفرم اما چاره ای نیست) async و await به عهده خود کامپایلر میباشد ما را ملزم میکند که اگر موافق باشید زمان هر چند کوتاهی را به بررسی اجمالی روش استفاده از آن اختصاص بدهیم.
در ابتدا برای استفاده از این Keyword ها توجه به سه شرط اساسی ، اجتناب ناپذیر است:
1- متد مشخص شده با asunc حتما باید دارای یک خروجی از نوع Task یا Task<T> و حتی حداقل void باشد
2- متد مورد نظر ما که برای انجام عملیات Asynchronous انتخاب میشود حتما باید در ابتدای متد صریحا Keyword جدید async را به عنوان یک Modifier ذکر نماید. وجود آن به تنهایی تضمین کننده انجام عملیات به صورت Async نیست اما در صورت عدم ذکر آن کامپایلر، قطعا متد ما را در حالت Synchronous اجرا خواهد کرد
3- این عملیات همیشه و در همه حال Discontinuous و منقطع می باشد و چنانچه در مثال خواهیم دید ذکر دستور await اجرای عملیات را به مسیرهای متفاوت و بعضا نا آشنایی مثل وضعیت تعلیق (Suspension) هدایت میکند که این لزوما خبر بدی نیست اما نیاز به مراقبت و کنترل های خاص دارد که به بیراهه کشیده نشود.

 

class Program
{
static void Main()
{
Task showNumber = ShowEvenNumbers();
Console.ReadLine();
}
public static async Task ShowEvenNumbers()
{
await Task.Run(() => Console.WriteLine("Async Function"));
}
}

 

من در این مثال عمدا سایر جزییات غیر ضروری را حذف نموده و فقط در خلاصه ترین شکل ممکن نمونه سادهای از نحوه استفاده از Syntax و الگوی اصلی را مشخص نموده ام.
توجه به نقش محوری کلاس Task و وجود و نحوه استفاده از Keywordهای async و await در این مثال اهمیت زیادی دارد.
اینک من برای پرهیز از عذاب وجدان لازم میدانم که با چند تغییر مختصر آن را با برخی از دانسته های خود که در بحث های قبلی به آنها اشاره کردیم ترکیب کرده و نتیجه را در مثال دیگری نشان بدهم . قطعا تفسیر و تحلیل این کد دیگر نیازی به توضیح اضافی نخواهد داشت.

 

class Program
{
static void Main()
{
Task showNumber = ShowEvenNumbers();
Console.ReadLine();
}
public static async Task<int> ShowEvenNumbers()
{
return await Task.Run(() =>
{
Console.WriteLine("Async Function");
});
}
}

 

من در قسمت اول این رشته از مطالب اشاره کردم که اساسا انگیزه اصلی من در بررسی اجمالی سیر تحول روشهای Asynchronous Programming در .NET تاکید بسیار زیاد Microsoft در قسمت وسیعی از کدهای Platform اختصاصی و جدید آن به نام Windows 8 App Store بود که صرفا برای طراحی و پیاده سازی برنامه های Metro Style قابل اجرا در Windows 8 تاکید بسیار زیادی بر روی آن وجود دارد. موضوع عدم انتظار UI در این Platform و با در نظر گرفتن معماری Metro Style به صورت بسیار جدی در لایه های محافظتی خود OS و با تکنیکهای بسیار پیچیده ای نظیر تعلیق و تجدید نظر در معماری برنامه ها از اهمیت بسیار بالایی برخوردار است.
نظر شخصی من در مورد این Platform جدید مایکروسافت این است که اولا آینده بسیار درخشانی خواهد داشت و ثانیا تعمیم ایده بسیار موفق ارائه برنامه ها بدون واسطه در فروشگاههای مجازی نرم افزار (که تجربیات موفق Google و Apple را به همراه دارد) روشهای نوینی در تکنولوژی های تولید برنامه Microsoft معرفی میکند. به همین دلیل در خاتمه با ذکر یک مثال ساده در این Platform مجموعه مباحث مرتبط با این موضوع را به پایان خواهم رساند.
در این مثال هدف Serialize و DeSerialize یا به تعبیری نوشتن و خواندن یک Dataی ساده در Platform جدید مایکروسافت است که در مورد دستیابی به منابع سیستم مثل دیسک و حافظه ذخیره سازی مخصوص و ایزوله شده ای به نام Isolation Storage Area استراتژی های خاص و بسیار متفاوتی نسبت به گذشته دارد و در این مورد سختگیریهایی زیادی انجام میدهد.
شاید عجیب تر ین خبر درباره این Platform این باشد که هیچ اثری از System.Data.SqlClient نیست و معنی دقیق این گفته این است که امکان دسترسی با روشهای مستقیم به Microsoft Sql Server به هیچ وجه وجود ندارد مگر با استفاده از WCF و آن هم با تدابیر بسیار متفاوت.
این کد مستقیما از کتاب Real World Windows 8 Development اثر Samidip Basu از انتشارات APress درج شده است. به نظرم عملیات اصلی کد تا اندازه بسیار زیادی گویا بوده و بنابراین از توضیحات اضافی در این باره پرهیز میکنم.

 

private async void SaveCustomData()
{
MemoryStream customDataToSave = new MemoryStream();
DataContractSerializer serializer = new
DataContractSerializer(typeof(ObservableCollection<ApressBook>));
serializer.WriteObject(customDataToSave, FeaturedBookListVM.FeaturedApressBooks);
// Write serialized custom data to File on HardDisk.
StorageFile fileToWrite = await
localFolder.CreateFileAsync("CustomSerializedFile.xml", CreationCollisionOption.ReplaceExisting);
using (Stream fileStream = await fileToWrite.OpenStreamForWriteAsync())
{
customDataToSave.Seek(0, SeekOrigin.Begin);
await customDataToSave.CopyToAsync(fileStream);
await fileStream.FlushAsync();
}
}
private async void ReadCustomData()
{
StorageFile fileToRead = await localFolder.GetFileAsync("CustomSerializedFile.xml");
using (IInputStream inStream = await fileToRead.OpenSequentialReadAsync())
{
// Read data from File and Deserialize.
DataContractSerializer serializer = new
DataContractSerializer(typeof(ObservableCollection<ApressBook>));
FeaturedBookListVM.FeaturedApressBooks =
(ObservableCollection<ApressBook>)serializer.ReadObject(inStream.AsStreamForRead());
}
}

 

موافقین ۰ مخالفین ۰ ۹۳/۰۷/۳۰
مهران حسین نیا

نظرات  (۱)

مچکرم. خیلی برای من مفید بود.

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی