ماهو نمط الـ Thread Pools (الـ Executors)
بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته
في الدرسين السابقين تعرفنا على طريقة انشاء خيوط حاسوبية بإستخدام النمطين الـ انشاء نمط الـ 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. وحتى لايطول الحديث في هذا الدرس سنتعرف على طريقة استخدام الاخرين في دروس لاحقه.
رابط الكلاسات المستخدمه في هذا الدرس:
للحصول على رابط المشروع راجع درس المقدمة.
المصادر والمراجع:
- ThreadPoolExecutor | Android Developers.
- ExecutorService | Android Developers.
- Executor | Android Developers.
- Executors | Android Developers.
- A Guide to the Java ExecutorService.
للمزيد راجع درس المقدمة.
نهاية الدرس
لاتنسى تتبع الدرس والدورة كذلك لإشعارك عندما يتم التعديل على المتحوى او اضافة المزيد من المعلومات. ايضاً لاتنسى الاعجاب بالدرس ومشاركته مع الاخرين.
محتوى الدورة
الكلمات الدليلية
عن الدرس
1 إعجاب |
2 متابع |
0 مشاركة |
4089 مشاهدات |
منذ 5 سنوات |
التعليقات (0)
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !