ماهو نمط الـ Thread Pools (الـ Executors)

Mohammad Laifمنذ 3 سنوات

بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته

 

في الدرسين السابقين تعرفنا على طريقة انشاء خيوط حاسوبية بإستخدام النمطين الـ انشاء نمط الـ Handler و Looper و Thread و الـ انشاء نمط الـ HandlerThreads. في هذا الدرس سنتعرف على طريقة انشاء الخيوط الحاسوبية بإستخدام نمط الـ Thread Pools اي بركة الخيوط والذي يسمى بعض الاحيان بالـ Executors.

 

ماهو الـ Thread Pools
بالاساس لايوجد شئ يسمى Thread Pools بل المقصود هو Executor اما بالنسبة لتسميته بـ Thread Pools فهي عبارة عن استراتيجيه ومن خلال اسمها نستطيع القول بانها عبارة عن بركة من الخيوط!

 

فائدة الـ Thread Pools
في الدروس السابقة احتجنا الى انشاء خيط حاسوبي في كل مره نريد تشغيل مهمه خارج الخيط الحاسوبي الرئيسي, وانشاء هذه الخيوط يكلف ومتعب. لذلك تأتي هذه الاستراتيجيه لإنشاء بركه من الخيوط ونستخدمهم ونحفظهم في هذه البركه لنعيد استخدامهم مره اخرى اذا احتجنا لهم (اي ان هذا النمط لايعيد تكرار انشاء الخيط الحاسوبي). لنستطيع اعادة استخدام الخيط الحاسوبي مره اخرى (Reusable). بعكس الطرق السابقة جميعها.

 

في ماذا تستخدم الـ Thread Pools
اكثر استخداماتها في:

  • التعامل مع سيرفرات الويب و الـ API.
  • التعامل مع قواعد البيانات من كتابة وقرائة وما الى ذلك.
  • تم استخدامها مؤخراً من قبل مبرمجين الـ Android SDK لجعل نمط الـ AsyncTask يقوم على الـ Executor.
  • يتم استخدامها في مكاتب كثيرة جداً من بعضها مكاتب تتعلق بمعالجة وتحميل الصور من شبكة الانترنت الى هاتفك وما شابهها من مكاتب كذلك.

 

في درس سابق بدورة "Android Architecture Components" بعنوان:  انشاء المستودع بـ LiveData استخدمنا الـ Thread Pools بنمط Singleton لتمكننا من تشغيل مهام الحفظ والقرائة لقاعدة البيانات الـ Room. في هذا الدرس سنقوم باستخدام مثال اخر سهل كبدايه لفهمه.


مكونات الـ Thread Pool في SDK الاندرويد
يوجد لدينا مكونات تساعدنا على انشاء واستخدام مفهوم الـ Thread Pools بسهولة ويسر وهي:

 

واجهة Executor

هذه الواجه هي الاساس لصناعة Thread Pool.

 

كلاس Executors

تعمل هذه الكلاس على implement للواجهه Executor وتضيف الكثير من الدوال المساعدة. اي انها كلاس مساعدة لإختصار الوقت لك.

 

واجهة ExecutorService

تعمل هذا الواجهه ايضاً على implement للواجهه Executor ولكن تضيف ميزات اخرى لها, واحد اهم هذه الميزات هي استخدام عنصر Callable لإرجاع قيمه مستقبليه.

 

عناصر مساعدة Callable

هل تذكر عنصرنا الـ Runnable؟ هذا مشابهه له ولكن الاختلاف هنا هو: ان الـ Callable يستطيع ارجاع قيمه (بعكس الـ Runnable لايستطيع ارجاع اي قيمه). ربما تريد الاطلاع على الدرس السابق بعنوان: مكونات الخيوط الحاسوبية (Thread).

 

عناصر مساعدة Future

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

 

انواع الـ Executors الشائعة في الـ Andriod SDK

  • الـ newSingleThreadExecutor: يستخدم لصنع خيط حاسوبي واحد قابل لإعادة الاستخدام.
  • الـ newFixedThreadPool: يستخدم لصنع خيوط حاسوبية متعددة قابلة لإعادة الاستخدام.
  • الـ newScheduledThreadPool: يستخدم لصنع خيط حاسوبي مؤقت قابل لإعادة الاستخدام.

 

استخدام الـ ThreadPools (النوع الـ scheduledExecutorService) لملئ قاعدة البيانات الـ Room ببيانات جاهزة

في درس سابق بدورة اخرى بعنوان: ملئ قاعدة البيانات الـ Room ببيانات جاهزه للمستخدم. رأينا كيفية ملئ قاعدة البيانات بالبيانات الجاهزة, وذلك من خلال انشاء عنصر RoomDatabase.Callback ثم استخدام AsyncTask لذلك. في هذا المثال سنقوم باستخدام ScheduledExecutorService وهي عباره عن طريقه لتطبيق ThreadPools لاغير.

 

في دالة الـ onCreate نقوم بإنشاء عنصر من الـ ScheduledExecutorService بهذا الشكل:

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

 

ثم نقوم بانشاء عنصر Runnable ونضع الشفرة التي تحتوي على البيانات الجاهزة هناك:

            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    NameDao nameDao = sInstance.getNameDao();
                    nameDao.insertName(new NameEntity("Ahmad1", "0555555551"));
                    nameDao.insertName(new NameEntity("Ahmad2", "0555555552"));
                    nameDao.insertName(new NameEntity("Ahmad3", "0555555553"));
                    nameDao.insertName(new NameEntity("Ahmad4", "0555555554"));
                    nameDao.insertName(new NameEntity("Ahmad5", "0555555555"));
                    nameDao.insertName(new NameEntity("Ahmad6", "0555555556"));
                    nameDao.insertName(new NameEntity("Ahmad7", "0555555557"));
                    nameDao.insertName(new NameEntity("Ahmad8", "0555555558"));
                    nameDao.insertName(new NameEntity("Ahmad9", "0555555559"));
                    nameDao.insertName(new NameEntity("Ahmad10", "05555555510"));
                    Log.d(TAG, "run: Data Created by ExecutorService scheduled");

                }
            }, 1, TimeUnit.SECONDS);

لاحظ استخدام النوع الـ ScheduledExecutorService الذي يسمح لنا بتأخير تشغيل هذه الشفرة البرمجية على الخيط الحاسوبي, وذلك من خلال استخدام TimeUnit كثانيه واحدة كما هو بالسطر الاخير.

 

لاننسى اطفاء هذا الـ Pool:

scheduledExecutorService.shutdown();

من الضروري اطفاء بركة الخيوط عندما تنتهي من استخدامها.

 

في حالة استخدام الـ AsyncTask سيكون الوضع هكذا:

            new PopulateDbAsync(sInstance).execute();
            AsyncTask<Void, Void, Void> aa = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... voids) {
                    NameDao nameDao = sInstance.getNameDao();
                    nameDao.insertName(new NameEntity("Mohammad1", "0555555551"));
                    nameDao.insertName(new NameEntity("Mohammad2", "0555555552"));
                    nameDao.insertName(new NameEntity("Mohammad3", "0555555553"));
                    nameDao.insertName(new NameEntity("Mohammad4", "0555555554"));
                    nameDao.insertName(new NameEntity("Mohammad5", "0555555555"));
                    nameDao.insertName(new NameEntity("Mohammad6", "0555555556"));
                    nameDao.insertName(new NameEntity("Mohammad7", "0555555557"));
                    nameDao.insertName(new NameEntity("Mohammad8", "0555555558"));
                    nameDao.insertName(new NameEntity("Mohammad9", "0555555559"));
                    nameDao.insertName(new NameEntity("Mohammad10", "05555555510"));
                    return null;
                }
            };

            aa.execute();

على كل حال فضل استخدام الـ Executor عند التعامل مع الـ Room.

 

الكلاس كاملة:

package com.mzdhr.androidthreadbackgroundtasks.database;

import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;

import com.mzdhr.androidthreadbackgroundtasks.database.dao.NameDao;
import com.mzdhr.androidthreadbackgroundtasks.database.entity.NameEntity;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by MohammadL on 08/1/2019
 * Contact me at [email protected]
 */
@Database(entities = {NameEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

    private static final String TAG = "AppDatabase";
    private static final Object LOCK = new Object();
    private static final String DATABASE_NAME = "namesdb";
    private static AppDatabase sInstance;


    public static AppDatabase getInstance(Context context) {
        if (sInstance == null) {
            synchronized (LOCK) {
                Log.d(TAG, "getInstance: Creating a new database instance");
                sInstance = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        AppDatabase.DATABASE_NAME
                )

                        .addCallback(sRoomDatabaseCallback) // populate database
                        .build();
            }
        }
        Log.d(TAG, "getInstance: Getting the database instance, no need to recreated it.");
        return sInstance;
    }

    public abstract NameDao getNameDao();

    private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {

        @Override
        public void onCreate(@NonNull SupportSQLiteDatabase db) {
            super.onCreate(db);

            // Using executors
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    NameDao nameDao = sInstance.getNameDao();
                    nameDao.insertName(new NameEntity("Ahmad1", "0555555551"));
                    nameDao.insertName(new NameEntity("Ahmad2", "0555555552"));
                    nameDao.insertName(new NameEntity("Ahmad3", "0555555553"));
                    nameDao.insertName(new NameEntity("Ahmad4", "0555555554"));
                    nameDao.insertName(new NameEntity("Ahmad5", "0555555555"));
                    nameDao.insertName(new NameEntity("Ahmad6", "0555555556"));
                    nameDao.insertName(new NameEntity("Ahmad7", "0555555557"));
                    nameDao.insertName(new NameEntity("Ahmad8", "0555555558"));
                    nameDao.insertName(new NameEntity("Ahmad9", "0555555559"));
                    nameDao.insertName(new NameEntity("Ahmad10", "05555555510"));
                    Log.d(TAG, "run: Data Created by ExecutorService scheduled");

                }
            }, 1, TimeUnit.SECONDS);

            scheduledExecutorService.shutdown();
        }

        @Override
        public void onOpen(@NonNull SupportSQLiteDatabase db) {
            super.onOpen(db);
        }
    };
}

 

لاحظ استخدام تقنية القفل Lock في كلاس قاعدة البيانات الـ Room وكلمة synchronized كذلك. حتى لايحدث احد اخطاء التزامن (الـ Concurrency) الشائعة.

 

في هذا الدرس تعرفنا على نمط الـ Thread Pools المسمى بالـ Executor. وفوائده واشهر انواعه. مع مثال بسيط لإستخدامه لملئ قاعدة البيانات الـ Room. وحتى لايطول الحديث في هذا الدرس سنتعرف على طريقة استخدام الاخرين في دروس لاحقه.

 

رابط الكلاسات المستخدمه في هذا الدرس:

للحصول على رابط المشروع راجع درس المقدمة.
 

المصادر والمراجع:

للمزيد راجع درس المقدمة.
 

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

المحاضر

Mohammad Laif

محتوى الدورة

تمهيد
1 مقدمة
2 تعرف على التزامن (الـ Concurrency)
3 اخطاء التزامن (الـ Concurrency) الشائعة
العمليات Processes
1 الهيكلة الهندسية لبيئة نظام الاندرويد
2 طبقة الـ Android Runtime و العمليات Processes
3 انواع العمليات (Processes)
الخيوط الحاسوبية Threads
1 الخيوط الحاسوبية (Threads)
2 مكونات الخيوط الحاسوبية (Thread)
3 انشاء الخيط الحاسوبي
أنماط التصميم للخيوط الحاسوبية
1 انشاء نمط الـ Handler و Looper و Thread
2 انشاء نمط الـ HandlerThreads
3 ماهو نمط الـ Thread Pools (الـ Executors) الدرس الحالي
4 استخدام نمط الـ Thread Pools كـ Singleton
5 استخدام الـ Callable مع نمط الـ Thread Pools
6 انشاء بركة خيوط حاسوبية Thread Pools لتسريع قاعدة البيانات
7 انشاء نمط الـ AsyncTask
8 انشاء نمط الـ Loader
الـ Broadcasts
1 تعرف على الـ Broadcast Receiver
2 انشاء الـ Broadcast Receiver بشكل ثابت
3 انشاء الـ Broadcast Receiver بشكل ديناميكي
4 استخدام الـ Local Broadcast للتخاطب بين المكونات
الـ Services
1 تعرف على الـ Services
2 انشاء الـ Started Service
3 انشاء الـ Intent Service
4 انشاء الـ Bound Service
5 استخدام الـ ResultReceiver للتخاطب مع الـ Intent Service
6 استخدام الـ Broadcast للتخاطب مع الـ Started Service
الـ Alarm Manager
1 تعرف على الـ Alarm Manager
2 طرق استخدام الـ Alarm Manager
الـ Jobs
1 استخدام الـ Android JobSchedualer
2 استخدام الـ Firebase JobDispatcher
3 استخدام الـ WorkManager من حزمة JetPack

الكلمات الدليلية

عن الدرس

1 إعجاب
2 متابع
0 مشاركة
2612 مشاهدات
منذ 3 سنوات

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !