![]() |
|
البرمجة المتوازية في دلفي - نسخة قابلة للطباعة +- delphi4arab منتديات دلفي للعرب (https://delphi4arab.net/forum) +-- قسم : منتديات دلفي للعرب التعليمية (https://delphi4arab.net/forum/forumdisplay.php?fid=3) +--- قسم : دلفي للعرب - القسم العام (https://delphi4arab.net/forum/forumdisplay.php?fid=10) +--- الموضوع : البرمجة المتوازية في دلفي (/showthread.php?tid=13029) |
البرمجة المتوازية في دلفي - الفجر الابيض - 27-01-2026 السلام عليكم ورحمة الله من الامور التي ازعجتني كثيرا في دلفي هي عندما احتجت لان ادخل في استخدام برمجة متوازية للعمليات التي كنت بحاجة الى ادارتها والبيانات التي كنت ارسلها واستقبلها في .net الادارة سهلة وكل شئ يتم بدون تدخل المستخدم ولكن في دلفي يجب ان تكون حذرا جدا في هذا وتدير كل شئ من بدايته الى نهايته يدويا وبحرص شديد لندخل في الموضوع تعتبر الـ Tasks في دلفي جزءا اساسيا من مكتبة البرمجة المتوازية (Parallel Programming Library - PPL) التي تم تقديمها لتسهيل التعامل مع تعدد المهام (Multi-threading). الهدف منها هو تنفيذ عمليات برمجية في الخلفية دون ان يتوقف البرنامج عن الاستجابة. فيما يلي شرح مبسط وشامل حول كيفية استخدامها: 1. ما هي الـ Task؟ ببساطة، الـ Task هي وحدة عمل يتم ارسالها لتعمل بشكل منفصل عن الخيط الرئيسي (Main Thread). بدلا من انشاء TThread يدويا وادارة دورة حياته، تقوم الـ Task بادارة ذلك تلقائيا باستخدام "مجمع الخيوط" (Thread Pool)، مما يوفر استهلاكا اقل لموارد الجهاز. 2. كيفية الاستخدام الاساسية لاستخدام الـ Tasks، يجب عليك اضافة الوحدة System.Threading الى قائمة الـ uses. مثال عملي: كود : uses System.Threading;
procedure TForm1.Button1Click(Sender: TObject);
var
aTask: ITask;
begin
// انشاء وبدء المهمة
aTask := TTask.Create(procedure
begin
// كود يستغرق وقتا طويلا هنا
Sleep(3000);
// العودة لتحديث الواجهة البرمجية بامان
TThread.Queue(nil, procedure
begin
ShowMessage('تم الانتهاء من المهمة بنجاح!');
end);
end);
aTask.Start;
end;3. مميزات استخدام الـ Tasks سهولة الكود: لا تحتاج لتعريف كلاس جديد لكل خيط. الادارة الذكية: يقوم النظام بتحديد عدد الخيوط بناء على قدرة المعالج. عدم تجميد الشاشة: يبقى المستخدم قادرا على الضغط على الازرار وتحريك النافذة اثناء العمل. 4. نصائح هامة جدا تحديث الواجهة: ابدا لا تقم بتغيير خصائص المكونات (مثل Label.Caption) مباشرة من داخل الـ Task. استخدم دائما TThread.Synchronize او TThread.Queue. المتغيرات المحلية: كن حذرا عند استخدام المتغيرات التي قد تختفي من الذاكرة قبل انتهاء المهمة. معالجة الاخطاء: يفضل استخدام try..except داخل الـ Task لان الاخطاء بداخلها قد لا تظهر بشكل مباشر للمستخدم. 5. انتظار مجموعة من المهام (WaitAll) يمكنك تشغيل عدة مهام والانتظار حتى تنتهي جميعا قبل الانتقال للخطوة التالية: كود : var
tasks: array of ITask;
begin
SetLength(tasks, 2);
tasks[0] := TTask.Create(procedure begin Sleep(1000); end);
tasks[0].Start;
tasks[1] := TTask.Create(procedure begin Sleep(2000); end);
tasks[1].Start;
// الانتظار حتى تنتهي المهمتان
TTask.WaitForAll(tasks);
ShowMessage('كل المهام اكتملت');
end;كما هو مذكور اي شئ يمس الواجهة ابدا ومطلقا ولا تحاول ان تلمسه وانت خارج من TThread.Queue(nil, procedure نفذ المهمة التي تحتاج الى وقت طويل بداخل كود : aTask := TTask.Create(procedure
beginوعندما تنتهي منها ارجع الى الواجهة وافعل ماتريد هنا كود : TThread.Queue(nil, procedure
begin
ShowMessage('تم الانتهاء من المهمة بنجاح!');
end);وكما تلاحظون لم اقم بقتل الكائن Free وهذا لان في دلفي، عند التعامل مع الـ Tasks، نحن لا نستخدم Free يدوياً لأن الـ ITask هو عبارة عن Interface. إليك توضيح لهذه النقطة بدون علامات تشكيل: إدارة الذاكرة التلقائية: بما أن الـ Task يتم التعامل معه كـ Interface، فإن دلفي تستخدم نظام (Reference Counting). هذا يعني أن الكائن يتم تدميره وتحرير ذاكرته تلقائياً بمجرد انتهاء العمل منه وعدم وجود أي متغير يشير إليه. دورة حياة المهمة: بمجرد انتهاء الإجراء (Procedure) الموجود داخل المهمة، وبمجرد أن يفقد المتغير aTask نطاقه (Scope)، يقوم النظام بتنظيف كل شيء خلفه. تجنب الأخطاء: محاولة تحرير (Free) المهمة يدوياً قد تؤدي إلى أخطاء في الذاكرة (Access Violation)، لأن "مجمع الخيوط" (Thread Pool) هو المسؤول عن إدارة عمر هذه الخيوط وإعادتها للمجموعة أو تدميرها. نصيحة إضافية: إذا كنت تستخدم كائنات أخرى (مثل TStringList أو TQuery) داخل المهمة، فهذه الكائنات يجب تحريرها يدوياً باستخدام try..finally و Free داخل كود المهمة نفسه، لأنها كائنات عادية وليست Interfaces. مثال سريع للتوضيح: كود : TTask.Run(procedure
var
List: TStringList;
begin
List := TStringList.Create;
try
// افعل شيئا بالقائمة
finally
List.Free; // هنا يجب التحرير يدويا للكائنات الداخلية فقط
end;
end);هذا شئ لا غنى عنه للتعامل مع قواعد البيانات على الانترنت او تحميل ورفع الملفات وبدون هذا الاسلوب فسوف تواجه مشاكل لا حد لها ودمتم بخير RE: البرمجة المتوازية في دلفي - الفجر الابيض - 31-03-2026 من الامور التي فيها مشكلة كبيرة مع دلفي هي البرمجة الغير متوازية ستواجهك مشاكل كثيرة في ذلك عند العمل مع تطبيقات كبيرة ومعقدة وضع خطين تحت كلمة ومتداخلة أي عمليات تعتمد على نتيجة عملية قبلها وانا اعمل في الدوت نت لم تكن هذه بالمشكلة لانها متقدمة جدا وكان يكفي ان اكتب كود : Dim result = Await TryLogin(TextEdit1.Text.Trim, TextEdit2.Text.Trim)
If result.IsSuccess Then
Dim kk = Await EmpTable.LoadEmpAsync(1)
If kk.IsSuccess Then
Else
End If
Else
End Ifاو بلغة سي شارب كود : var result = await TryLogin(TextEdit1.Text.Trim(), TextEdit2.Text.Trim());
if (result.IsSuccess)
{
var kk = await EmpTable.LoadEmpAsync(1);
if (kk.IsSuccess)
{
// ضع الكود هنا في حالة النجاح
}
else
{
// ضع الكود هنا في حالة الفشل
}
}
else
{
// ضع الكود هنا في حالة فشل تسجيل الدخول
}كود واضح وسهل والدوت نت ستقوم بعمل كل شئ عنك اما في دلفي وبما انك سوف تدير كل شئ يدويا تخيل انك سوف تنشي Task بداخل Task و وبعدها معالجة النتيجة بادخل TThread.Queue كما في الدروس السابقة ومعالجة الاستثاناءت واخيرا قتل كل الكائنات لخربطة الكود الناتج لن نتعب حتى انفسنا بكتابة مثال هنا ناهيك عن برمجة خاصة للانتظار سوف تضطر لاستخدام .Wait ولن تترك العملية تسير في تسلسل كانه صناديق تتساقط على بعضها اي انت بحاجة الى انتظار العملية الاولى ستكون العملية مثل كود : procedure TForm1.BtnOldWayClick(Sender: TObject);
begin
Label1.Caption := 'جاري البدء...';
// 1. يجب أن نفتح خيطاً كبيراً ليحتوي كل العمليات حتى لا تتجمد الشاشة
TTask.Run(procedure
var
Task1, Task2: ITask;
Res1, Res2: TOperationResult;
begin
try
// --- الخطوة الأولى ---
Task1 := TTask.Run(procedure
begin
Res1 := FetchFromAPI; // تشغيل الدالة الأولى
end);
Task1.Wait; // الانتظار الإجباري هنا (حبس الخيط الحالي)
// --- الخطوة الثانية (تعتمد على الأولى) ---
if Res1.IsSuccess then
begin
Task2 := TTask.Run(procedure
begin
Res2 := ProcessData(Res1); // تمرير نتيجة الأولى للثانية
end);
Task2.Wait; // الانتظار الإجباري مرة أخرى
end;
// --- تحديث الواجهة (يجب العودة للخيط الرئيسي يدوياً) ---
TThread.Queue(nil, procedure
begin
if Assigned(Res2) then
begin
Label1.Caption := 'النتيجة النهائية: ' + Res2.Message;
Res2.Free; // تنظيف الذاكرة
end
else if Assigned(Res1) then
begin
Label1.Caption := 'توقفنا عند الخطوة الأولى: ' + Res1.Message;
Res1.Free;
end;
end);
except
on E: Exception do
begin
TThread.Queue(nil, procedure
begin
Label1.Caption := 'خطأ غير متوقع: ' + E.Message;
end);
end;
end;
end);
end;طبعا كما ترى توجد اجراءات فرعية نستدعيها بداخل العمليات وطبعا يجب احترام العمليات في الاجراءات الطرفية وعدم مساس المسار الرئيسي للتطبيق باي شكل كما شرحنا سابقا و الان ما يهمكم من هذه الشرح وخاصة عنما ورد بثقنية لا علاقة لها بدلفي فقد اردت محاكاة ما يحدث في الدوت نت وتقريبه وتبسيطه الى اقصى حد ممكن في دلفي فما سنقوم ببنائه للتو يجمع بين ثلاثة من أقوى وأعقد مفاهيم دلفي الحديثة: Generics (<T>)، و Anonymous Methods (TFunc)، و Multithreading. والتي انا ايضا لازالت احاول اثقانها وفهمها وسوف نتدرج بتجربة الافكار كما تدرجت فيها في تجاربي اولا لننشئ الوحدة كود : unit Async.Utils;
interface
uses
System.SysUtils, System.Threading, System.Classes;
type
TAsync = class
public
// لتشغيل وظيفة تعيد قيمة
class function Run<T>(Func: TFunc<T>): IFuture<T>;
// محاكاة الـ Await عبر Callback أنيق لضمان عدم تجميد الواجهة
class procedure Await<T>(Future: IFuture<T>; OnComplete: TProc<T>); overload;
// لتشغيل إجراء (Procedure) لا يعيد قيمة
class procedure Execute(Proc: TProc);
end;
implementation
{ TAsync }
class function TAsync.Run<T>(Func: TFunc<T>): IFuture<T>;
begin
Result := TTask.Future<T>(Func);
end;
class procedure TAsync.Await<T>(Future: IFuture<T>; OnComplete: TProc<T>);
begin
TTask.Run(procedure
var
Value: T;
begin
Value := Future.Value; // سينتظر هنا في خيط منفصل
TThread.Queue(nil, procedure
begin
OnComplete(Value); // العودة للخيط الرئيسي لتحديث الواجهة
end);
end);
end;
class procedure TAsync.Execute(Proc: TProc);
begin
TTask.Run(procedure
begin
Proc();
end);
end;
end.ضع زر و لايبيل على النمودج وفي الزر اكتب كود : procedure TForm1.Button1Click(Sender: TObject);
var
FutureResult: IFuture<Integer>;
begin
// 1. تشغيل العملية في الخلفية ببساطة
FutureResult := TAsync.Run<Integer>(function: Integer
begin
Sleep(3000); // محاكاة عملية ثقيلة أو جلب بيانات SQL
Result := 500;
end);
// 2. "الانتظار" الذكي دون تجميد البرنامج
TAsync.Await<Integer>(FutureResult, procedure(Res: Integer)
begin
// هذا الجزء سيعمل تلقائياً في الخيط الرئيسي بعد انتهاء المهمة
Label1.Caption := 'النتيجة هي: ' + Res.ToString;
end);
Label1.Caption := 'جاري التحميل...'; // ستظهر فوراً لأن Await لا تجميد
end;هذا ينفع للعمليات البسيطة ودالتين واحدة للتنفيذ والاخرى لانتظار النتيجة وتنفيد اي شئ بدون قلق من المساس بالواجهة طبعا ثقنيات Generics (<T>)، و Anonymous Methods (TFunc)، و Multithreading ليست موضوعنا وشرحها يحتاج الى موضوع لوحده ولكن اعتبر هذه الوحدات مكونات مغلفة تستخدمها ولا ترى كودها والمهم تؤدي لك العمل المطلوب الان سنطور الفكرة قليل ونستخدم وحدة اخرى RE: البرمجة المتوازية في دلفي - الفجر الابيض - 31-03-2026 كود : unit Async.Fluent;
interface
uses
System.SysUtils, System.Threading, System.Classes;
type
IAsyncOperation<T> = interface
['{F2C3D4E5-A1B2-C3D4-E5F6-AABBCCDDEEFF}']
function OnSuccess(Proc: TProc<T>): IAsyncOperation<T>;
function OnError(Proc: TProc<Exception>): IAsyncOperation<T>;
end;
TAsyncOperation<T> = class(TInterfacedObject, IAsyncOperation<T>)
private
FFunc: TFunc<T>;
FOnSuccess: TProc<T>;
FOnError: TProc<Exception>;
FSelfReference: IAsyncOperation<T>;
procedure Execute;
public
constructor Create(AFunc: TFunc<T>);
function OnSuccess(Proc: TProc<T>): IAsyncOperation<T>;
function OnError(Proc: TProc<Exception>): IAsyncOperation<T>;
class function Run(AFunc: TFunc<T>): IAsyncOperation<T>;
end;
TAsync = class
public
class function Call<T>(Func: TFunc<T>): IAsyncOperation<T>;
end;
implementation
{ TAsyncOperation<T> }
constructor TAsyncOperation<T>.Create(AFunc: TFunc<T>);
begin
inherited Create;
FFunc := AFunc;
end;
class function TAsyncOperation<T>.Run(AFunc: TFunc<T>): IAsyncOperation<T>;
var
Op: TAsyncOperation<T>;
begin
Op := TAsyncOperation<T>.Create(AFunc);
Result := Op;
// نضمن بقاء الكائن في الذاكرة حتى ينتهي الخيط
Op.FSelfReference := Result;
// تشغيل الخيط بعد ضمان اكتمال بناء الواجهة تماماً
TThread.ForceQueue(nil, procedure
begin
Op.Execute;
end);
end;
procedure TAsyncOperation<T>.Execute;
begin
TTask.Run(procedure
var
LResult: T;
begin
try
// تنفيذ الدالة المطلوبة
LResult := FFunc();
// العودة للخيط الرئيسي للنتائج
TThread.Queue(nil, procedure
begin
try
if Assigned(FOnSuccess) then
FOnSuccess(LResult);
finally
FSelfReference := nil; // التدمير الذاتي الآمن
end;
end);
except
on E: Exception do
begin
TThread.Queue(nil, procedure
var
LExc: Exception;
begin
try
if Assigned(FOnError) then
FOnError(E);
finally
FSelfReference := nil; // التدمير الذاتي الآمن
end;
end);
end;
end;
end);
end;
function TAsyncOperation<T>.OnSuccess(Proc: TProc<T>): IAsyncOperation<T>;
begin
FOnSuccess := Proc;
Result := Self;
end;
function TAsyncOperation<T>.OnError(Proc: TProc<Exception>): IAsyncOperation<T>;
begin
FOnError := Proc;
Result := Self;
end;
{ TAsync }
class function TAsync.Call<T>(Func: TFunc<T>): IAsyncOperation<T>;
begin
// نستخدم دالة Run لضمان تسلسل صحيح للذاكرة
Result := TAsyncOperation<T>.Run(Func);
end;
end.بنفس الطريقة زر ولايبيل واكتب فيه كود : TAsync.Call<string>(function: string
begin
Sleep(2000);
Result := 'تمت العملية بنجاح';
end)
.OnSuccess(procedure(Val: string)
begin
ShowMessage(Val);
end)
.OnError(procedure(E: Exception)
begin
ShowMessage('خطأ: ' + E.Message);
end);لاحظ كيف ان الكود صار اكثر تنظيما وفهما وابسط بكثير ولكن هذه مجرد عملية واحدة ماذا لو اردنا عملية تنتظر عملية اخرى وهو اساس موضوعنا طبعا تستطيع استخدام نفس الوحدة وتضع علمية بداخل عملية اخرى ولكنه ليس الاسلوب الامثل وسوف نكمل وهنا نحتاج الى وحدة مثل RE: البرمجة المتوازية في دلفي - الفجر الابيض - 31-03-2026 كود : unit Async.Promises;
interface
uses
System.SysUtils, System.Threading, System.Classes, Models.Response;
type
// 1. تعريف الواجهة (The Interface)
// نستخدم GUID صالح ونحول الإجراءات إلى وظائف تعيد الواجهة نفسها لتسمح بالتسلسل
IPromise<T> = interface
['{A1B2C3D4-E5F6-4A1B-8C2D-E3F4A5B6C7D8}']
function OnSuccess(Proc: TProc<T>): IPromise<T>;
function OnError(Proc: TProc<Exception>): IPromise<T>;
end;
// 2. تعريف الفئة المنفذة (The Implementation Class)
TPromise<T> = class(TInterfacedObject, IPromise<T>)
private
FTask: ITask;
FResult: T;
FError: Exception;
FOnSuccess: TProc<T>;
FOnError: TProc<Exception>;
FSelfRef: IPromise<T>; // لحماية الكائن من التدمير التلقائي أثناء العمل
public
constructor Create(Func: TFunc<T>);
// دالة الانتقال للخطوة التالية (تستخدم الكلاس مباشرة لتجنب قيود الواجهة في دلفي)
function ThenCall<TNext>(Func: TFunc<T, TNext>): TPromise<TNext>;
// تنفيذ دوال الواجهة
function OnSuccess(Proc: TProc<T>): IPromise<T>;
function OnError(Proc: TProc<Exception>): IPromise<T>;
// دالة داخلية للحماية والتشغيل
procedure ProtectAndStart;
// نقطة الانطلاق الساكنة
class function Start(Func: TFunc<T>): TPromise<T>;
end;
implementation
{ TPromise<T> }
constructor TPromise<T>.Create(Func: TFunc<T>);
begin
inherited Create;
// إنشاء المهمة (Task)
FTask := TTask.Create(procedure
begin
try
// تنفيذ الكود المطلوب في الخلفية
FResult := Func();
// العودة للخيط الرئيسي لتنفيذ الـ Callback
TThread.Queue(nil, procedure
begin
try
if Assigned(FOnSuccess) then
FOnSuccess(FResult);
finally
FSelfRef := nil; // تحرير الكائن بعد الانتهاء بنجاح
end;
end);
except
on E: Exception do
begin
FError := E;
TThread.Queue(nil, procedure
begin
try
if Assigned(FOnError) then
FOnError(FError);
finally
FSelfRef := nil; // تحرير الكائن بعد حدوث خطأ
end;
end);
end;
end;
end);
end;
procedure TPromise<T>.ProtectAndStart;
begin
FSelfRef := Self; // نطلب من الكائن أن يمسك نفسه في الذاكرة
FTask.Start; // ابدأ التنفيذ الفعلي
end;
class function TPromise<T>.Start(Func: TFunc<T>): TPromise<T>;
begin
Result := TPromise<T>.Create(Func);
Result.ProtectAndStart;
end;
function TPromise<T>.ThenCall<TNext>(Func: TFunc<T, TNext>): TPromise<TNext>;
var
CurrentStep: TPromise<T>;
begin
CurrentStep := Self;
// إنشاء الوعد التالي (Step Next)
Result := TPromise<TNext>.Create(function: TNext
begin
// انتظر انتهاء الخطوة الحالية
CurrentStep.FTask.Wait;
// إذا حدث خطأ في الخطوة السابقة، ارفعه للأعلى ليتم التقاطه في OnError
if CurrentStep.FError <> nil then
raise CurrentStep.FError;
// تنفيذ الخطوة الحالية باستخدام نتيجة الخطوة السابقة
Result := Func(CurrentStep.FResult);
end);
// حماية وتشغيل الخطوة التالية
Result.ProtectAndStart;
end;
function TPromise<T>.OnSuccess(Proc: TProc<T>): IPromise<T>;
begin
FOnSuccess := Proc;
Result := Self; // إرجاع الواجهة للسماح بإضافة .OnError بعدها
end;
function TPromise<T>.OnError(Proc: TProc<Exception>): IPromise<T>;
begin
FOnError := Proc;
Result := Self; // إرجاع الواجهة للسماح بإضافة .OnSuccess بعدها
end;
end.
//unit Async.Promises;
//
//interface
//
//uses
// System.SysUtils, System.Threading, System.Classes, Models.Response;
//
//type
// // الواجهة التي تحدد ما يمكن للمستخدم رؤيته في النهاية
// IPromise<T> = interface
// ['{A1B2C3D4-E5F6-4A1B-8C2D-E3F4A5B6C7D8}']
// procedure OnSuccess(Proc: TProc<T>);
// procedure OnError(Proc: TProc<Exception>);
// end;
//
// // الكلاس المنفذ
// TPromise<T> = class(TInterfacedObject, IPromise<T>)
// private
// FTask: ITask;
// FResult: T;
// FError: Exception;
// FOnSuccess: TProc<T>;
// FOnError: TProc<Exception>;
// FSelfRef: IPromise<T>;
// public
// constructor Create(Func: TFunc<T>);
//
// // دالة التسلسل
// function ThenCall<TNext>(Func: TFunc<T, TNext>): TPromise<TNext>;
//
// // دالة الحماية والتشغيل (هذه ستحل مشكلة الـ Access والـ Type Mismatch)
// procedure ProtectAndStart;
//
// procedure OnSuccess(Proc: TProc<T>);
// procedure OnError(Proc: TProc<Exception>);
//
// class function Start(Func: TFunc<T>): TPromise<T>;
// end;
//
//implementation
//
//{ TPromise<T> }
//
//constructor TPromise<T>.Create(Func: TFunc<T>);
//begin
// inherited Create;
// FTask := TTask.Create(procedure
// begin
// try
// FResult := Func();
// TThread.Queue(nil, procedure
// begin
// try
// if Assigned(FOnSuccess) then FOnSuccess(FResult);
// finally
// FSelfRef := nil; // تحرير الكائن
// end;
// end);
// except
// on E: Exception do
// begin
// FError := E;
// TThread.Queue(nil, procedure
// begin
// try
// if Assigned(FOnError) then FOnError(FError);
// finally
// FSelfRef := nil; // تحرير الكائن
// end;
// end);
// end;
// end;
// end);
//end;
//
//procedure TPromise<T>.ProtectAndStart;
//begin
// // هنا النوع T يطابق تماماً النوع T الخاص بالـ SelfRef
// FSelfRef := Self;
// FTask.Start;
//end;
//
//class function TPromise<T>.Start(Func: TFunc<T>): TPromise<T>;
//begin
// Result := TPromise<T>.Create(Func);
// Result.ProtectAndStart; // استدعاء دالة الحماية والتشغيل
//end;
//
//function TPromise<T>.ThenCall<TNext>(Func: TFunc<T, TNext>): TPromise<TNext>;
//var
// Current: TPromise<T>;
//begin
// Current := Self;
//
// // إنشاء الوعد التالي
// Result := TPromise<TNext>.Create(function: TNext
// begin
// Current.FTask.Wait; // انتظر الخطوة السابقة
// if Current.FError <> nil then raise Current.FError;
// Result := Func(Current.FResult);
// end);
//
// // تشغيل الوعد التالي وحمايته باستخدام الدالة العامة ProtectAndStart
// Result.ProtectAndStart;
//end;
//
//procedure TPromise<T>.OnSuccess(Proc: TProc<T>);
//begin
// FOnSuccess := Proc;
//end;
//
//procedure TPromise<T>.OnError(Proc: TProc<Exception>);
//begin
// FOnError := Proc;
//end;
//
//end.بنفس الطريقة زر ولايبيل وضع الكود كود : Label3.Caption := 'جاري العمل في الخلفية...';
TPromise<TOperationResult>.Start(function: TOperationResult
begin
// الخطوة 1: استدعاء الدالة الأولى
Result := FetchFromAPI;
end)
.ThenCall<TOperationResult>(function(Prev: TOperationResult): TOperationResult
begin
// الخطوة 2: استدعاء الدالة الثانية وتمرير نتيجة الأولى لها
Result := ProcessData(Prev);
end)
.OnSuccess(procedure(Final: TOperationResult)
begin
// تحديث الواجهة بالنتيجة النهائية
if Final.IsSuccess then
Label3.Caption := 'نجاح: ' + Final.Message
else
Label3.Caption := 'فشل: ' + Final.Message;
// تحرير الكائن النهائي (آخر حلقة في السلسلة)
Final.Free;
end)
.OnError(procedure(E: Exception)
begin
// في حال حدوث خطأ غير متوقع في أي مرحلة
Label3.Caption := 'خطأ حرج: ' + E.Message;
end);الان صار بامكاننا استدعاء ThenCall عدة مرات بالتسلسل الذي نريده ولشرح ما يجري هنا سارفق لكم الاجراءات الفرعية للتوضيح لدينا الاجراء يجلب ID كود : function TForm1.FetchFromAPI: TOperationResult;
begin
// ننشئ نتيجة افتراضية (فشل)
Result := TOperationResult.Create(False, 'بداية العملية');
try
// محاكاة وقت انتظار الشبكة (2 ثانية)
Sleep(2000);
// لنفترض أننا جلبنا معرف موظف جديد
Result.IsSuccess := True;
Result.Message := 'تم الاتصال بالسيرفر وجلب البيانات';
Result.NewId := 550;
except
on E: Exception do
begin
Result.IsSuccess := False;
Result.Message := 'خطأ في الاتصال: ' + E.Message;
end;
end;
end;RE: البرمجة المتوازية في دلفي - الفجر الابيض - 31-03-2026 في العملية الثانية سوف نستخدم هذا ID بعملية فرعية كود : function TForm1.ProcessData(const PrevResult: TOperationResult): TOperationResult;
begin
// ننشئ نتيجة جديدة بناءً على ما وصلنا من الخطوة السابقة
Result := TOperationResult.Create(False);
try
// محاكاة معالجة بيانات (1 ثانية)
Sleep(1000);
if PrevResult.IsSuccess then
begin
// نقوم بعملية ما على الـ ID الذي وصلنا
Result.IsSuccess := True;
Result.NewId := PrevResult.NewId + 100; // مثال: تحديث المعرف
Result.Message := 'تمت معالجة البيانات بنجاح. المعرف المحدث: ' + Result.NewId.ToString;
end
else
begin
Result.IsSuccess := False;
Result.Message := 'لم يتم المعالجة لأن الخطوة السابقة فشلت';
end;
finally
// ملاحظة مهمة: يجب تحرير الكائن السابق (الذي جاء من الخطوة الأولى)
// لأننا لن نحتاجه بعد الآن في السلسلة
PrevResult.Free;
end;
end;ولدينا هذه الفئة التي نستخدمها لتمرير البارمترات والقيم التي نحتاجها كود : unit Models.Response;
interface
uses
System.SysUtils, REST.Json.Types;
type
TOperationResult = class
private
FIsSuccess: Boolean;
FMessage: String;
FNewId: Integer;
FException: Exception;
public
[JsonPropertyName('status')]
property IsSuccess: Boolean read FIsSuccess write FIsSuccess;
[JsonPropertyName('message')]
property Message: String read FMessage write FMessage;
[JsonPropertyName('new_id')]
property NewId: Integer read FNewId write FNewId;
property Exception: Exception read FException write FException;
constructor Create(ASuccess: Boolean; AMsg: String = ''; AID: Integer = 0);
end;
implementation
constructor TOperationResult.Create(ASuccess: Boolean; AMsg: String; AID: Integer);
begin
FIsSuccess := ASuccess;
FMessage := AMsg;
FNewId := AID;
end;
end.وهكذا ستجد في التطبيق يعمل بسلاسة تامة والواجهة مرتاحة ولا تجمد فيها وكودك واضح تماما طبعا هذه الطرق جاءت نتيجة تجارب متعددة اضعها لكم هنا بالفائدة النهائية ، و يمكن تطوير هذه الوحدات ولازالت يمكن جعلها اكثر احترافية وتقدم والله الموفق طبعا هذه الوحدات تنشئها مرة واحدة وتستخدمها بالطبيق لكل العمليات واي عمليات مهما كان نوعها ووظيفتها والامثلة هنا يمكنك تجربتها ونسخها ووضع زر و label ورؤية النتيجة والتي تحتاج الى الاجراءات الفرعية انشئ لها الاجراءات هنا فمن خلال التجربة يمكنك ملاحظة النتيجة والمهمة |