نگاشت APM به یک Task
در قسمت اول، نمونه مثالی را از APM، که در آن کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید، مشاهده کردید. در ادامه میخواهیم یک محصور کنندهی جدید را برای این نوع API قدیمی تهیه کنیم، تا آنرا به صورت یک Task ارائه دهد.
public static class ApmWrapper { public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count) { return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null); } }
در مثال فوق BeginRead و EndRead استفاده شده از نوع delegate هستند. چون خروجی EndRead از نوع int است، خروجی متد نیز از نوع Task of int تعیین شدهاست. همچنین سه پارامتر ابتدایی BeginRead ، دقیقا data، offset و count هستند. دو پارامتر آخر آن callback و state نام دارند. پارامتر callback توسط متد FromAsync فراهم میشود و state نیز در اینجا null درنظر گرفته شدهاست.
یک مثال استفاده از آنرا در ادامه مشاهده میکنید:
using System; using System.IO; using System.Threading.Tasks; namespace Async06 { public static class ApmWrapper { public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count) { return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null); } } class Program { static void Main(string[] args) { using (var stream = File.OpenRead(@"..\..\program.cs")) { var data = new byte[10000]; var task = stream.ReadAsync(data, 0, data.Length); Console.WriteLine("Read bytes: {0}", task.Result); } } } }
البته همانطور که پیشتر نیز عنوان شد، استفاده از خاصیت Result، اجرای کد را بجای غیرهمزمان بودن، به حالت همزمان تبدیل میکند.
در اینجا چون خروجی متد ReadAsync یک Task است، میتوان از متد ContinueWith نیز بر روی آن جهت دریافت نتیجه استفاده کرد:
using (var stream = File.OpenRead(@"..\..\program.cs")) { var data = new byte[10000]; var task = stream.ReadAsync(data, 0, data.Length); task.ContinueWith(t => Console.WriteLine("Read bytes: {0}", t.Result)).Wait(); }
یک نکته
پروژهی سورس بازی به نام Async Generator در GitHub، سعی کردهاست برای ساده سازی نوشتن محصور کنندههای مبتنی بر Task روش APM، یک Code generator تولید کند. فایلهای آنرا از آدرس ذیل میتوانید دریافت کنید:
نگاشت EAP به یک Task
نمونهای از Event based asynchronous pattern یا EAP را در قسمت اول، زمانیکه روال رخدادگردان webClient.DownloadStringCompleted را بررسی کردیم، مشاهده نمودید. کار کردن با آن نسبت به APM بسیار سادهتر است و نتیجهی نهایی عملیات غیرهمزمان را در یک روال رخدادگران، در اختیار استفاده کننده قرار میدهد. همچنین در روش EAP، اطلاعات در همان Synchronization Context ایی که عملیات شروع شدهاست، بازگشت داده میشود. به این ترتیب اگر آغاز کار در ترد UI باشد، نتیجه نیز در همان ترد دریافت خواهد شد. به این ترتیب دیگر نگران دسترسی به مقدار آن در کارهای UI نخواهیم بود؛ اما در APM چنین ضمانتی وجود ندارد.
متاسفانه TPL همانند روش FromAsync معرفی شده در ابتدای بحث، راه حل توکاری را برای محصور سازی متدهای روش EAP ارائه ندادهاست. اما با استفاده از امکانات TaskCompletionSource آن میتوان چنین کاری را انجام داد. در ادامه سعی خواهیم کرد همان متد الحاقی توکار DownloadStringTaskAsync ارائه شده در دات نت 4.5 را از صفر بازنویسی کنیم.
public static class WebClientExtensions { public static Task<string> DownloadTextTaskAsync(this WebClient web, string url) { var tcs = new TaskCompletionSource<string>(); DownloadStringCompletedEventHandler handler = null; handler = (sender, args) => { web.DownloadStringCompleted -= handler; if (args.Cancelled) { tcs.SetCanceled(); } else if(args.Error!=null) { tcs.SetException(args.Error); } else { tcs.SetResult(args.Result); } }; web.DownloadStringCompleted += handler; web.DownloadStringAsync(new Uri(url)); return tcs.Task; } }
سپس از TaskCompletionSource برای تبدیل این عملیات به یک Task کمک میگیریم. اگر args.Cancelled مساوی true باشد، یعنی عملیات دریافت فایل لغو شدهاست. بنابراین متد SetCanceled منبع Task ایجاد شده را فراخوانی خواهیم کرد. این مورد استثنایی را در کدهای فراخوان سبب میشود. به همین دلیل بررسی خطا با یک if else پس از آن انجام شدهاست. برای بازگشت خطای دریافت شده از متد SetException و برای بازگشت نتیجهی واقعی دریافتی، از متد SetResult میتوان استفاده کرد.
به این ترتیب متد الحاقی غیرهمزمان جدیدی را به نام DownloadTextTaskAsync برای محصور سازی متد EAP ایی به نام DownloadStringAsync و همچنین رخدادگران آن تهیه کردیم.