الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تعرف على المهام Tasks (جديد دوت نت 4)

بدأه عبد العظيم بخاري في 11 سبتمبر 2010 · 1 رد · 925 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

microsoft_.net_logo.png

تضمنت الدوت نت 4 مجموعة من الكلاسات الجديدة التي تجرد اعمال الthreading وتنظمها بصورة مميزة . هذه الكلاسات جاءت ضمن namespace جديدة تسمى ب System.Threading.Tasks .

في الواقع الية عمل هذه الكلاسات تستخدم ThredPool في الخلفية وبشكل عام تمثل المهمة task جزء من اعمال معينة يجب أن تنفذ وهذا الجزء يمكن تشغيله في thread منفصلة مع العلم أنه يمكن تشغيل المهمة باسلوب متزامن والذي يقضي بانتظار الthread المستدعاة .

تستطيع باستخدامك للمهام tasks أن تمتلك طبقة للتحكم بالthreads التحتية وايضاً مرونة اكبر لتنظيم العمل فمثلاً يمكنك تعريف مهام استمرارية والتي يجب أن تبدأ عندما تنتهي مهمة ما بل حتى يمكنك التحكم بشكل اكبر وتحديد وقت تنفيذ المهام الإستمرارية في حالات نجاح أو فشل تلك المهمة . يمكنك ايضا تنظيم المهام ضمن هرم يحتوي في رأسه على المهمة الأب وجميع المهام تحته (التي تنشئها تلك المهمة) تكون بمثابة الأبناء لها مما ينشأ نوعاً من التبعة فمثلاً عند قيامك بالغاء المهمة الأب سيتم الغاء مهام الأبناء .

كيفية بدء مهمة task

يمكنك استعمال TaskFactory لبدء مهمة ما أو يمكنك استدعاء الconstructor للTask وبعده ميثود Start() لتقوم بنفس العمل .

فعندما تبدأ مهمة ما يتم انشاء instance من كلاس Task ويتم ربط الكود الذي تريد تشغيله مع تلك المهمة (باستعمال الdelegate :

(Action أو Action<object>

والذي يعني اما أن تكون الميثود (التي تحتوي الكود الذي نريد تشغيله) بدون اي باراميتر أو ذات باراميتر object واحد (مثل الذي نعرفه عن كلاس Thread)

الكود التالي يوضح كيف عرفنا الميثود بدون باراميتر ثم طبعنا الID للمهمة :

//انظر للكود الكامل اخر الدرس
static void TaskMethod()
{
Console.WriteLine("running in a task");
Console.WriteLine("Task id: {0}", Task.CurrentId);
}

يمكنك رؤية طرق مختلفة لبدء مهمة جديدة في الكود السابق . الطريقة الأولى هي بواسطة عمل instance ل TaskFactory حيث يتم تمرير الميثود TaskMethod إلى ميثود StartNew() ويتم انشاء المهمة task فوراً .

الطريقة الثانية هي باستعمال الconstructor للكلاس Task وعندما يتم عمل instance فلا يتم بدء الtask مباشرة بل عندئذ يتم اعطاءها حالة created ولكي يتم بدئها يجب استدعاء الميثود Start() من الكلاس Task .

يمكنك ايضاً استدعاء الميثود RunSynchronously() لبدء المهمة لكن في هذه الحالة فإن المهمة سوف تعمل ضمن الthred الحالية للمستدعي وعليه فإن المستدعي يجب أن ينتظر انتهاء المهمة , اما في الوضع الطبيعي فالمهمة سوف تعمل بشكل غير متزامن مع المستدعي .

// using task factory
TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod);
// using the task factory via a task
Task t2 = Task.Factory.StartNew(TaskMethod);
// using Task constructor
Task t3 = new Task(TaskMethod);
t3.Start()

يمكنك مع الconstructor Task والميثود StartNew() لل TaskFactory من تمرير قيم من الenum الذي يدعى TaskCreationOption . فبوضع اخيار LongRunning على سبيل المثال فإنك تبلغ الجدول الزمني للمهمة أنها تأخذ وقت طويل لذلك سوف يقوم ذلك الجدول على الأرجح باستعمال thread جديدة .

اما اذا اردت أن تربط مهمة ما بمهمة اب وبالتالي تلغى في حال الغاء المهمة الأب هنا يجب عليك وضع الخيار AttachToParent .

اما الخيار PreferFairness تعني أن الجدول الزمني يجب أن يأخذ المهام الاولى التي تنتظر حالياً وبالطبع هذه ليست الحالة الإفتراضية في حال تم انشاء مهمة داخل مهمة اخرى .

اذا انشئت المهام عمل اضافي باستخدام مهام أبناء فسيتم تفضيل وتقديم مهام الأبناء على المهام الاخرى لذلك لن يكونوا على قائمة الإنتظار في طابةر الthredpool من اجل التنفيذ .

اذا ادرت لهذه المهام ان تعالج بنفس اولوية المهام الاخرى عليك بوضع الخيار PreferFairness.

Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness);
t4.Start();

المهام الإستمرارية Continuation Tasks

يمكنك في المهام تحديد أنه بعد انتاء مهمة ما فمهمة اخرى يجب أن تعمل بعدها . هذا الأمر مفيد في حال كان هناك مهمة جديدة تستعمل ناتج مهمة سابقة أو تقوم ببعض التنظيفات للذاكرة وغيرها في حال فشلت المهمة الأولى.

عندما كنا نتعامل مع المهام العادية كنا لا نضع أي باراميتر او نضع باراميتر واحد من نوع object اما في حالة المهام الإستمرارية نضع باراميتر من نوع Task وهنا يمكنك الوصول لمعلومات المهمة الأصلية :

//انظر للكود الكامل اخر الدرس
static void DoOnFirst()
{
Console.WriteLine("doing some task {0}", Task.CurrentId);
Thread.Sleep(3000);
}
static void DoOnSecond(Task t)
{
Console.WriteLine("task {0} finished", t.Id);
Console.WriteLine("this task id {0}", Task.CurrentId);
Console.WriteLine("do some cleanup");
Thread.Sleep(3000);
}

يتم تعريف المهمة الإستمرارية عن طريق استدعاء ميثود ContinueWith على الtask ويمكنك ايضاً استعمال TaskFactory لنفس الغاية.

t1.ContinueWith(DoOnSecond)

هذا السطر يعني أن مهمة جديدة تستدعي الميثود DoOnSecond() يجب أن تبدأ بعد انتهاء المهمة t1 . يمكنك ايضاً بدأ مهام متعددة عندما تنتهي مهمة واحدة . ويمكن للمهمة الإستمرارية أن تمتلك مهمة استمرارية اخرى كما يوضح المثال التالي :

Task t1 = new Task(DoOnFirst);
Task t2 = t1.ContinueWith(DoOnSecond);
Task t3 = t1.ContinueWith(DoOnSecond);
Task t4 = t2.ContinueWith(DoOnSecond);

تبدأ المهام الإستمرارية دائماً عندما تنتهي المهمة السابقة بغض النظر عن الطريقة التي انتهت فيها . يمكنك مع الخيارات الموجودة في TaskContinutionOptions أن تعرف أن المهمة الإستمرارية يجب أن تبدأ فقط اذا نجحت المهمة السابقة (أو فشلت).

من الخيارات التي يمكن استعمالها : OnlyOnfaulted و NotOnFaulted و OnlyOnCanceld و NotOnCanceld و

OnlyOnRanToCompletion .

Task t5 = t1.ContinueWith(DoOnError,
TaskContinuationOptions.OnlyOnFaulted);

تدرج المهام

تعلمنا أنه مع المهام يمكننا أن نبدأ مهمة بعد انتهاء اخرى . ايضاً يمكن للمهام أن تشكل هرم , فعندما تبدأ مهمة مهمة جديدة يتم انشاء هرم يتكون من اب وابن .

لاحظ الكودات التالية أنه تم انشاء مهمة جديدة من المهمة الأب والكود يوضح أن اناء مهمة ابن هي بنفس طريقة انشاء مهمة اب لكن بفرق وحيد وهو أن مهمة الإبن يتم انشائها داخل مهمة اخرى

//انظر للكود الكامل اخر الدرس
static void ParentAndChild()
{
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
}
static void ParentTask()
{
Console.WriteLine("task id {0}", Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child");
}
static void ChildTask()
{
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished");
}

في حال انتهت المهمة الأب قبل انتهاء المهمة الابن سيم اظهار حالة المهمة الأب ك WaitingForChildrenToComplete ويتم انهاء المهمة الأب مع حالة RanToCompletion كحال اي مهمة ابن تنتهي ايضاً . طبعا هذا الأمر لن يكون هكذا في حال كانت المهمة الأب قد أنشأت مهمة بخيار DetachedFromParent .

يجب أن تعلم ايضاً أن الغاء المهمة الأب ستؤدي لالغاء الأبناء (سنتكلم عن اطر الإلغاء Cancellation framework في درس اخر ان شاء الله).

ارجاع نتائج من المهام

عندما تنتهي مهمة ما يمكنك ارسال بعض المعلومات إلى كائن مشترك و الذي يجب أن يكون thread-safe طريقة اخرى لعمل ذلك وهي باستعمال مهمة ترجع نتيجة , فمع اصدار الgeneric من كلاس Task من الممكن تعريف نوع يتم ارجاعه من مهمة ترجع نتيجة

يمكن التصريح عن الميثود التي يتم استدعائها من المهمة لارجاع ناتج يمكن التصريح عنها بأي نوع مرجع return type .

يرجع الميثود TaskWithResult في المثال التالي قيمتي int بمساعدة Tuple . الداخل للميثود يجب أن يكون void أو من نوع boject كما هو مبين:

//الكود الكامل اخر الدرس
static Tuple<int, int> TaskWithResult(object division)
{
Tuple<int, int> div = (Tuple<int, int>)division;
int result = div.Item1 / div.Item2;
int reminder = div.Item1 % div.Item2;
Console.WriteLine("task creates a result...");
return Tuple.Create<int, int>(result, reminder);
}

عندما نعرف مهمة لاستدعاء الميثود TaskWithResult نستعمل كلاس generic وهو

Task<TResult>

حيث أن باراميتر الgeneric يعرف النوع المرجع .

يتم تمرير الميثود مع الConstructor للFunc delegate والباراميتر الثاني يعرف القيمة المدخلة .

ولأن المهمة تحتاج لقيمتين مدخلتين في الباراميتر object فإنه تم انشاء tuple ايضاً وبعدها يتم بدء المهمة .

تنتظر الProperty التي تدعى Result الinstance t1 من كلاس Task حتى تنتهي المهمة وبعدها سوف تحتوي هذه الproperty على الناتج من المهمة.

var t1 = new Task < Tuple < int,int > > (TaskWithResult,
Tuple.Create < int, int > (8, 3));
t1.Start();
Console.WriteLine(t1.Result);
t1.Wait();
Console.WriteLine("result from task: {0} {1}", t1.Result.Item1,
t1.Result.Item2);

الكود الكامل :

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

namespace TaskExamples
{
  class Program
  {
    static void Main()
    {
      // ParallelDemo();

      SimpleTask();
      // ContinuationTask();
      // ParentAndChild();
      // ResultsFromTasks();

      Thread.Sleep(5000);

      //ParentAndChild();

      // HierarchyTasks("c:\\");

      //Parallel.f
      //Task t1 = new Task(() => Console.WriteLine("running in a task"));
      //Task t2 = new Task(() => Console.WriteLine("running in a task"));

      //for (int i = 0; i < 10; i++)
      //{
      //  Task t1 = new Task(o =>
      //  {
      //    Console.WriteLine("running in a task {0}", Thread.CurrentThread.ManagedThreadId);
      //    Thread.Sleep(500);
      //    Console.WriteLine("still running {0}", Thread.CurrentThread.ManagedThreadId);
      //  }, "data", TaskCreationOptions.None);
      //  // t1.RunSynchronously();
      //  t1.Start();



      //}



      //Console.WriteLine("start sleep main");
      //Thread.Sleep(3000);
      //Console.WriteLine("main thread");
    }

    static void ResultsFromTasks()
    {
      var t1 = new Task<Tuple<int,int>>(TaskWithResult, Tuple.Create<int, int>(8, 3));
      t1.Start();
      Console.WriteLine(t1.Result);
      t1.Wait();
      Console.WriteLine("result from task: {0} {1}", t1.Result.Item1, t1.Result.Item2);
    }

    static Tuple<int, int> TaskWithResult(object division)
    {
      Tuple<int, int> div = (Tuple<int, int>)division;
      int result = div.Item1 / div.Item2;
      int reminder = div.Item1 % div.Item2;
      Console.WriteLine("task creates a result...");

      return Tuple.Create<int, int>(result, reminder);
    }

    static void SimpleTask()
    {
      // using task factory
      TaskFactory tf = new TaskFactory();
      Task t1 = tf.StartNew(TaskMethod);

      // using the task factory via a task
      Task t2 = Task.Factory.StartNew(TaskMethod);

      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

      // using Task constructor
      Task t3 = new Task(TaskMethod);
      // t3.Start();
      t3.RunSynchronously();

      Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness);
      t4.Start();



    }

    static void ContinuationTask()
    {
      Task t1 = new Task(DoOnFirst);
      Task t2 = t1.ContinueWith(DoOnSecond);
      Task t3 = t1.ContinueWith(DoOnSecond);
      Task t4 = t2.ContinueWith(DoOnSecond);   
      Task t5 = t1.ContinueWith(DoOnError, TaskContinuationOptions.OnlyOnFaulted);
      t1.Start();


      Thread.Sleep(5000);

    }


    static void DoOnFirst()
    {
      Console.WriteLine("doing some task {0}", Task.CurrentId);
      Thread.Sleep(3000);
    }

    static void DoOnSecond(Task t)
    {
      Console.WriteLine("task {0} finished", t.Id);
      Console.WriteLine("this task id {0}", Task.CurrentId);
      Console.WriteLine("do some cleanup");
      Thread.Sleep(3000);
    }

    static void DoOnError(Task t)
    {
      Console.WriteLine("task {0} had an error!", t.Id);
      Console.WriteLine("my id {0}", Task.CurrentId);
      Console.WriteLine("do some cleanup");
    }

    static void ParentAndChild()
    {
      Task parent = new Task(ParentTask);
      parent.Start();
      Thread.Sleep(2000);
      Console.WriteLine(parent.Status);
      Thread.Sleep(4000);
      Console.WriteLine(parent.Status);

    }
    static void ParentTask()
    {
      Console.WriteLine("task id {0}", Task.CurrentId);
      Task child = new Task(ChildTask); // , TaskCreationOptions.DetachedFromParent);
      child.Start();
      Thread.Sleep(1000);
      Console.WriteLine("parent started child");
      // Thread.Sleep(3000);
    }
    static void ChildTask()
    {
      // Console.WriteLine("task id {0}, parent: {1}", Task.Current.Id, Task.Current.Parent.Id);
      Console.WriteLine("child");
      Thread.Sleep(5000);
      Console.WriteLine("child finished");
    }

    static void TaskMethod()
    {
      Console.WriteLine("running in a task");
      Console.WriteLine("Task id: {0} {1}", Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
    }

    static void ParallelDemo()
    {
      // Parallel.For(0, 5, i => Console.WriteLine(i));
      Parallel.For<string>(0, 20, () => "abcd",
        (x, ls, str) =>
        {
          Console.WriteLine(x);
          return "defg";
        },
          (str) =>
          {
            Console.WriteLine("action {0}", str);
          });

      ParallelOptions po = new ParallelOptions();




    }



    //static void ParentAndChild()
    //{
    //  TaskFactory factory = new TaskFactory();
    //  var t1 = factory.StartNew(() =>
    //    {
    //      Console.WriteLine("parent task {0}", Task.CurrentId);

    //      factory.StartNew(() =>
    //        {
    //          Console.WriteLine("child task {0}", Task.CurrentId);
    //          Thread.Sleep(2000);
    //          Console.WriteLine("finished child");
    //        }, TaskCreationOptions.AttachedToParent);

    //      Console.WriteLine("finished parent");
    //    });

    //  t1.Wait();

    //}


  }
}

عبد العظيم بخاري

www.el-bukhari.com

3

مواضيع مشابهة