في مقالة سابقة بعنوان الخيوط الحاسوبية في الاندرويد تعرفنا على الخيط الحاسوبي ومماذا يتكون. في هذه المقالة سنرى الفرق بين مكونين مهمين على الخيط الحاسوبي وهما المشغل (Runnable) والمنادى (Callable) ومايرافقه من كائنات مستقبلية (Future Object) وطريقة استخدامهم في الاندرويد.

لفهم المقال بشكل افضل يجب عليك ان تكون عارفاً بالـ Android Architecture وقاعدة البيانات الـ Room. وايضاً معرفه بسيطه ببعض اوامر الـ SQLite (مشابهه للـ SQL). تستطيع قرائة مجموعة من الدروس قد كتبتها سابقاً في موقع عالم البرمجة المميز على الرابط التالي Android Architecture Components (دروس لتعلم Android Architecture Components من حزمة Android JetPack بلغة الجافا لتصميم تطبيقات الاندرويد.


ايضاً لفهم المقال يجب عليك معرفة التزامن بلغة الجافا Concurrency. يوجد دورة اقوم بكتابتها حالياً بعنوان: التزامن في نظام الاندرويد في موقع عالم البرمجة.

 

 مفهوم الـ Runnable والـ Callable والـ Future من الدرس السابق

الـ Runnable

عبارة عن واجهه Interface من خلالها نقوم بتغليف الشفرة التي نريد ارسالها على الخيط للمعالج. وتحتوي هذة الواجهة على دالة واحدة وهي run() نضع بداخلها الشفرة المراد تغليفها. ثم نقوم بارسال هذا الـ Runnable على الخيط الحاسوبي باستخدام Handler.

الـ Runnable عبارة عن كائن يغلف الشفرة البرمجية لإرسالها الى المعالج.

لقرائة الـ Documentation تفضل بزيارة صفحته على الرابط التالي: Runnable.

الـ Callable

الـ Callable مشابه للـ Runnable ولكن الفرق هو انه يستطيع ارجاع قيمة ما (بعكس الـ Runnable لايستطيع ارجاع اي قيمه) وتغلف تلك القيمة في عنصر مستقبلي.

لقرائة الـ Documentation تفضل بزيارة صفحته على الرابط التالي: Callable.

الـ Future عنصر مستقبلي للبيانات

 تغلف القيمة التي يقوم الـ Callable بإرجاعها في عنصر من هذا نوع.

لقرائة الـ Documentation تفضل بزيارة صفحته على الرابط التالي: Future.

اذن نستطيع الفهم ان:

  • المشغل؟ نستطيع فهم الـ Runnable على انه عنصر يغلف الشفرة البرمجية ومن خلاله نستطيع تشغيلها على الخيط الحاسوبي, اي كانه يجري بها على الخيط.
  • المنادى؟ نستطيع فهم الـ Callable على انه ايضاً عنصر يغلف الشفرة البرمجية ومن خلاله نستطيع تشغيلها على الخيط الحاسوبي, اي كانه يجري بها على الخيط الحاسوبي مع ميزة مضافه على الـ Runnable وهي اننا نستطيع ندائه عندما يصل الى المعالج ليخبرنا بالنتيجه.
  • الكائن المستقبلي؟ نستطيع فهم الـ Future على انه عنصر يغلف النتيجه التي يصرح بها الـ Callable وهو في قلب المعالج, بعد ان تتم الشفرة البرمجية التي يحملها الـ Callable. اذا لم تتم او وجد خلل في الشفرة او قمنا بإلغائه فأنه سوف يقوم برمي Exception ما.

استخدام الـ Runnable و الـ Callable في الاندرويد

والان لنتمكن من فهم الفروقات حول هذين الشيئين سنقوم باستخدامهم في مثالين لحفظ العناصر في قاعدة البيانات الـ Room.

ماذا سنستخدم؟

  • سنقوم باستخدام الدالة Insert في الـ Dao من مكتبة قاعدة البيانات الـ Room. في كلا المثالين. فمن الملاحظ في فقرة الـ Docs الخاص بالـ Insert انها تستطيع ارجاع قيمة Long للصف الذي تم اضافته في قاعدة البيانات وايضاً تستطيع عدم ارجاع اي شئ Void:

If the @Insert method receives only 1 parameter, it can return a long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return long[] or List<Long> instead.

  • سنقوم ايضاً باستخدام نمط الـ Repository مع قاعدة البيانات.
  • سنقوم باستخدام الـ Thraed Pools للدخول على قاعدة البيانات, فكما هو ملاحظ يمنع منعاً باتعاً استخدام قاعدة البيانات على الخيط الرئيسي, لذلك سنقوم سنستخدم Executors لصنع لنا خيط حاسوبي اخر بشكل سريع.

طريقة استخدام الـ Runnable

يوجد انماط عديدة لإستخدام الـ Runnable ولكن هنا سوف نستخدمة مع نمط الـ Thread Pools اي الـ Executors.

الخطوات:

انشاء ThreadPool تحتوي على خيط حاسوبي واحد:

ExecutorService executorService = Executors.newSingleThreadExecutor();

انشاء Runnable يغلف الشفرة البرمجية المراد تشغيلها, وهي هنا شفرة دالة الـ Insert التي تكتب في قاعدة البيانات الـ Room:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        mNameDao.insertName(nameEntity);
    }
};

ارسال الـ Runnable على الخيط الحاسوبي المنشئ من قبل الـ Executor كما بالشكل التالي:

executorService.execute(runnable);

الدالة كاملة:

طريقة استخدام الـ Callable مع الـ Future

كالحال مع استخدام الـ Runnable فسوف نستخدم نمط Thread Pools اي Executors.

الخطوات:

تجهيز عنصر من نوع Long حتى نسند قيمة الحقل المدخل له لاحقاً:

Long insertedColumnId = -1L;

انشاء ThreadPool تحتوي على خيط حاسوبي واحد:

ExecutorService executorService = Executors.newSingleThreadExecutor();

انشاء Callable يغلف الشفرة البرمجية المراد تشغيلها, وهي هنا شفرة دالة الـ Insert التي تكتب في قاعدة البيانات الـ Room: ولكن الفرق هنا سوف نجعلها تقوم بإرجاع عنصر Long.

Callable<Long> callable = new Callable<Long>() {
    @Override
    public Long call() throws Exception {
        return mNameDao.insertAndReturnColumnId(nameEntity);
    }
};

ارسال الـ Callable على الخيط الحاسوبي المنشئ من قبل الـ Executor كما بالشكل التالي (لاداعي بالقيام بهذة الخطوة فالخطوة القادمة تقوم محلها):

executorService.submit(callable);

نقوم بإنشاء عنصر مستقبلي حتى نستند القيمة التي يقوم بإرجاعها الـ Callable:

Future<Long> future = executorService.submit(callable);

 

الاستعلام عن البيانات من خلال عنصر الـ Future:

try {
    insertedColumnId = future.get(1, TimeUnit.SECONDS);
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    e.printStackTrace();
}

الدالة كاملة:

public Long insertRetrieveColumnId(final NameEntity nameEntity) {
    // Prepare column id
    Long insertedColumnId = -1L;

    // Create the Callable object that carry our code to the executor
    Callable<Long> callable = new Callable<Long>() {
        @Override
        public Long call() throws Exception {
            return mNameDao.insertAndReturnColumnId(nameEntity);
        }
    };

    // Create an executor
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    // Create a Future object
    Future<Long> future = executorService.submit(callable);

    // Retrieve the result into our future object, within 1 second or cancel it.
    // .get() -> Blocks the Thread, so we should put a max duration for it.
    // We can use future.isDone() to know if the task is done or not before using future.get().
    try {
        insertedColumnId = future.get(1, TimeUnit.SECONDS);
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
    }

    // Return the column id
    return insertedColumnId;
}

 

هكذا راينا كيفية استخدام الـ Runnable و الـ Callable ومن الملاحظ ان الفرق بينهم الـ Callable يقوم بإرجاع قيمة.


0 Comments

اترك رد