در مطلب دیگری در مورد برنامه نویسی همزمان و ناهمزمان صحبت کردیم، یک مثال ساده را با هم جلو رفتیم و مقداری از تفاوت هایش رو بیان کردیم. اما، در دنیای ناهمزمان، گذینه های زیادی برای استفاده در اختیارمان قرار دارد.اما چه زمانی باید از هر کدام استفاده کنیم؟
در دات نت 4 تابع اصلی برای زمانبندی یک تسک Task.Factory.StartNew بود. پیاده سازی های متنوع این تابع، پارامترهای زیاد که هر کدام مقادیر مختلف داشت، استفاده از آن را پیچیده کرده . باید بدانید که در چه زمانی از کدام پیاده سازی استفاده کنید و مقدار هر پارامتر را چه چیزی تنظیم کنید.
در نتیجه در دات نت 4.5 تابع جدید Task.Run معرفی شد. این تابع به هیچ وجه استفاده از Task.Factory.StartNew را منسوخ نکرد ولی یک استفاده آسان از این تابع رو در دسترس قرار داد، بدون اینکه نیاز باشد پارامترهای متعدد آنرا بشناسیم. در واقع Task.Run دقیقا بزینس مشابه Task.Factory.StartNew دارد، فقط، یک سری پارامترهای پیش فرض را مقدار دهی کرده .
Task.Run(someAction);
دقیقا معادل کد زیر است:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
به این ترتیب ، Task.Run میتواند و باید برای موارد متداول و ساده بعضی از تسک ها، برای پردازش در ThreadPool استفاده شود و این به معنی استفاده نکردن از Task.Factory.StartNew نیست. این تابع خیلی مهم و پیشرفته تر است. شما با پارامتر TaskCreationOptions رفتار تسک را مشخص میکنین. زمانبندی اون را تنظیم میکنید که در کجای صف و اجرا قرار بگیرد.
در ادامه سعی میکنم برای هر کدام از استفاده های این توابع مثالی بیاوریم که چه کاربردی دارند.
یک مثال ساده را در نظر بگیرید که میخواهیم تعداد فولدر ها و فایلهای موجود در یک پوشه را به صورت async در خروجی نمایش بدیم.
using System; using System.IO; using System.Threading.Tasks; public class Example { static void Main() { TaskFactory(); TaskRun(); } static void TaskFactory() { Task[] tasks = new Task[2]; String[] files = null; String[] dirs = null; String docsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); tasks[0] = Task.Factory.StartNew(() => files = Directory.GetFiles(docsDirectory)); tasks[1] = Task.Factory.StartNew(() => dirs = Directory.GetDirectories(docsDirectory)); Task.Factory.ContinueWhenAll(tasks, completedTasks => { Console.WriteLine("Task.Factory.StartNew"); Console.WriteLine("{0} contains: ", docsDirectory); Console.WriteLine(" {0} subdirectories", dirs.Length); Console.WriteLine(" {0} files", files.Length); }).Wait(); // Task.Factory.StartNew // C:\Users\ghodrati\Documents contains: // 17 subdirectories // 8 files } static void TaskRun() { Task[] tasks = new Task[2]; String[] files = null; String[] dirs = null; String docsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); tasks[0] = Task.Run(() => files = Directory.GetFiles(docsDirectory)); tasks[1] = Task.Run(() => dirs = Directory.GetDirectories(docsDirectory)); Task.WhenAll(tasks).ContinueWith(completedTasks => { Console.WriteLine("Task.Run"); Console.WriteLine("{0} contains: ", docsDirectory); Console.WriteLine(" {0} subdirectories", dirs.Length); Console.WriteLine(" {0} files", files.Length); }).Wait(); // Task.Run // C:\Users\ghodrati\Documents contains: // 17 subdirectories // 8 files } }
هر دو کد نمونه ی بالایک کار رو انجام میدهند. لازمه که توی کد بالا به نکته ای دقت داشته باشین.
استفاده از Wait() ترد جاری رو بلاک میکند تا اجرای تسک به پایان برسد و بعد به اجرای کدهای دیگر میپردازد.
تابع ContinueWhenAny() در Task.Factory این قابلیت را به شما میدهد که هر زمان یکی از تسک ها به پایان رسید به کار خود ادامه بدید. به عنوان مثال در نمونه کد بالا تابع TaskFactory رو به صورت زیر تغییر بدید:
static void TaskFactory() { Task[] tasks = new Task[2]; String[] files = null; String[] dirs = null; String docsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); tasks[0] = Task.Factory.StartNew(() => { Thread.Sleep(2000); files = Directory.GetFiles(docsDirectory); }); tasks[1] = Task.Factory.StartNew(() => dirs = Directory.GetDirectories(docsDirectory)); Task.Factory.ContinueWhenAny(tasks, completedTasks => { Console.WriteLine("Task.Factory.StartNew"); Console.WriteLine("{0} contains: ", docsDirectory); Console.WriteLine(" {0} subdirectories", dirs.Length); Console.WriteLine(" {0} files", files?.Length); }); // Task.Factory.StartNew // C:\Users\ghodrati\Documents contains: // 17 subdirectories // files }
همون طور که میبینید تسک مربوط به filesبه دلیل 2 ثانیه تاخیری که در اجرا دارد در خروجی نمایش داده نمیشود.
تابع WhenAny() در Taskهم، به همین صورت عمل میکند:
static void TaskRun() { Task[] tasks = new Task[2]; String[] files = null; String[] dirs = null; String docsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); tasks[0] = Task.Run(() => { Thread.Sleep(2000); files = Directory.GetFiles(docsDirectory); }); tasks[1] = Task.Run(() => dirs = Directory.GetDirectories(docsDirectory)); Task.WhenAny(tasks).ContinueWith(completedTasks => { Console.WriteLine("Task.Run"); Console.WriteLine("{0} contains: ", docsDirectory); Console.WriteLine(" {0} subdirectories", dirs.Length); Console.WriteLine(" {0} files", files?.Length); }); // Task.Run // C:\Users\ghodrati\Documents contains: // 17 subdirectories // files }