delphi4arab منتديات دلفي للعرب

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


من الامور التي ازعجتني كثيرا في دلفي هي عندما احتجت لان ادخل في استخدام برمجة متوازية للعمليات التي كنت بحاجة الى ادارتها والبيانات التي كنت ارسلها واستقبلها

في .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);


هذا شئ لا غنى عنه للتعامل مع قواعد البيانات على الانترنت او تحميل ورفع الملفات وبدون  هذا الاسلوب فسوف تواجه مشاكل لا حد لها
ودمتم بخير
من الامور التي فيها مشكلة كبيرة مع دلفي هي البرمجة الغير متوازية
ستواجهك مشاكل كثيرة في ذلك عند العمل مع تطبيقات كبيرة ومعقدة وضع خطين تحت كلمة ومتداخلة أي عمليات تعتمد على نتيجة عملية قبلها

وانا اعمل في الدوت نت لم تكن هذه بالمشكلة لانها متقدمة جدا وكان يكفي ان اكتب

كود :
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 ليست موضوعنا وشرحها يحتاج الى موضوع لوحده ولكن اعتبر هذه الوحدات مكونات مغلفة تستخدمها ولا ترى كودها والمهم تؤدي لك العمل المطلوب

الان سنطور الفكرة قليل ونستخدم وحدة اخرى
كود :
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);


لاحظ كيف ان الكود صار اكثر تنظيما وفهما وابسط بكثير

ولكن هذه مجرد عملية واحدة ماذا لو اردنا عملية تنتظر عملية اخرى وهو اساس موضوعنا
طبعا تستطيع استخدام نفس الوحدة وتضع علمية بداخل عملية اخرى ولكنه ليس الاسلوب الامثل وسوف نكمل

وهنا نحتاج الى وحدة مثل
كود :
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;
في العملية الثانية سوف نستخدم هذا 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 ورؤية النتيجة والتي تحتاج الى الاجراءات الفرعية انشئ لها الاجراءات هنا فمن خلال التجربة يمكنك ملاحظة النتيجة والمهمة