البرمجة المتوازية في دلفي
#1
السلام عليكم ورحمة الله


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

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


هذا شئ لا غنى عنه للتعامل مع قواعد البيانات على الانترنت او تحميل ورفع الملفات وبدون  هذا الاسلوب فسوف تواجه مشاكل لا حد لها
ودمتم بخير
إذا رأيت منتجاً مجانياً فأعلم بأنك أنت السّلعة
[-] كل من 4 users say قال شكرا ل الفجر الابيض على المشاركة المفيدة
  • أبو معاذ, S.FATEH, h-farid, Mr.DOS
الرد
#2
من الامور التي فيها مشكلة كبيرة مع دلفي هي البرمجة الغير متوازية
ستواجهك مشاكل كثيرة في ذلك عند العمل مع تطبيقات كبيرة ومعقدة وضع خطين تحت كلمة ومتداخلة أي عمليات تعتمد على نتيجة عملية قبلها

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

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

الان سنطور الفكرة قليل ونستخدم وحدة اخرى
إذا رأيت منتجاً مجانياً فأعلم بأنك أنت السّلعة
الرد
#3
كود :
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);


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

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

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


التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم