Delphi'de async / await: Olay İşleyicileri Olmadan sgcWebSockets | eSeGeCe Blog

Delphi'de async / await: Olay İşleyicileri Olmadan sgcWebSockets

· Bileşenler

Şimdiye kadar sgcWebSockets'teki her eşzamansız çağrı bir olay işleyicisi anlamına geliyordu. Arka plan iş parçacığında Get çağırıyordunuz, ya da Active := True ayarlayıp OnConnect olayını bekliyordunuz. Sonuç başka bir yerde, başka bir yöntemde geliyordu, dolayısıyla işlemi başlatan kod ile yanıtı tüketen kod farklı yerlerde yaşıyordu. Buna birincisine bağlı ikinci bir istek, artı hata işleme, artı yeniden gizlenmesi gereken bir "lütfen bekleyin" paneli ekleyin, sonunda formunuza yayılmış küçük bir durum makinesiyle baş başa kalırsınız: ne yaptığınızı hatırlamak için birkaç özel alan, son çağrının hâlâ devam edip etmediğini bilmek için bir bayrak ve üç istekten hangisinin geri döndüğünü anlamak zorunda olan bir işleyici.

sgcWebSockets 2026.7, bunun büyük kısmını ortadan kaldıran await tarzı bir API ekliyor. Bir HTTP isteği, bir WebSocket bağlantısı veya bir AI sohbet çağrısı artık bekleyebileceğiniz, geri çağrı zincirleyebileceğiniz veya iptal edebileceğiniz bir nesne döndürüyor, böylece kod gerçekleştiği sırada yukarıdan aşağıya okunuyor. İptal de göstermelik değil, yanıt sonunda geldiğinde onu yok saymak yerine altta yatan işlemi gerçekten durduruyor.

Görevler ve gelecekler

Yeni birim sgcBase_AsyncAwait. İki arayüz sunuyor. IsgcTask, biten ama bir değer üretmeyen işi temsil eder, IsgcFuture<T> ise T türünde bir değer üreten işi temsil eder. Her ikisi de size aynı dört şeyi verir: iş bitene kadar bloklamak için Await, başarılı olduğunda geri çağrı almak için ThenProc, bir istisna fırlattığında geri çağrı almak için OnError ve durdurmak için Cancel.

Kendinizinkini TsgcAsync.Run ile oluşturabilirsiniz. Geçirdiğiniz yordam veya fonksiyon bir arka plan iş parçacığında çalışır, geri çağrılar ise ana iş parçacığında döner, dolayısıyla ThenProc ve OnError içinde arayüze dokunmak güvenlidir.

uses
  sgcBase_AsyncAwait;

begin
  // the anonymous method runs on a background thread
  TsgcAsync.Run<Integer>(
    function: Integer
    begin
      Result := ExpensiveCalculation;
    end)
  .ThenProc(
    procedure(const Value: Integer)
    begin
      // main thread, safe to update the UI
      Label1.Caption := IntToStr(Value);
    end)
  .OnError(
    procedure(E: Exception)
    begin
      // main thread as well
      Label1.Caption := 'Failed: ' + E.Message;
    end);
end;

Görev çalışırken kendini canlı tutar, dolayısıyla yukarıdaki gibi başlat ve unut tarzı bir çağrı sorunsuzdur. Daha sonra incelemek isterseniz, arayüzü bir değişkende tutun ve IsCompleted, IsCancelled ya da tsaPending, tsaRunning, tsaCompleted, tsaFailed veya tsaCancelled değerlerinden biri olan State özelliğini okuyun. Ayrıca bir zaman aşımından sonra tamamlanan ve tetiklenmeden önce iptal edilebilen bir görev döndüren TsgcAsync.Delay(aMilliseconds) ve bir arka plan görevinin içinden bir kod parçasını ana iş parçacığına geri iten TsgcAsync.RunOnMainThread da vardır.

Bir HTTP isteğini beklemek

HTTP istemcisi gelecekleri doğrudan açığa çıkarır, dolayısıyla çağrıyı kendiniz sarmalamanız gerekmez. GetFuture ve PostFuture, yanıt gövdesine çözümlenen bir IsgcFuture<string> döndürür.

uses
  sgcHTTP_Client, sgcBase_AsyncAwait;

var
  oHTTP: TsgcHTTPClient;
  oFuture: IsgcFuture<string>;
begin
  oHTTP := TsgcHTTPClient.Create(nil);

  // the request runs on a background thread, this line returns immediately
  oFuture := oHTTP.GetFuture('https://httpbin.org/get');

  oFuture.ThenProc(
    procedure(const Value: string)
    begin
      // main thread: Value is the response body
      Memo1.Lines.Text := Value;
    end)
  .OnError(
    procedure(E: Exception)
    begin
      ShowMessage('GET failed: ' + E.Message);
    end);
end;

Zaten bir çalışan iş parçacığındaysanız ya da bir konsol uygulaması yazıyorsanız, basitçe bloklayıp değeri okuyabilirsiniz:

var
  vBody: string;
begin
  vBody := oHTTP.GetFuture('https://httpbin.org/get').Await;
  WriteLn(vBody);
end;

Await, arka plan çağrısının fırlattığı her şeyi, özgün sınıf adını ve mesajı taşıyan bir EsgcAsyncException içinde sarmalayarak yeniden fırlatır, dolayısıyla etrafındaki düz bir try ... except yine çalışır. Gelecek iptal edilmişse, Await bunun yerine EsgcTaskCancelled fırlatır.

Bu yazıdaki her şeyden daha önemli bir uyarı. Ana iş parçacığında Await çağırmak ana iş parçacığını bloklar, bu da bir VCL veya FMX uygulamasında çağrı dönene kadar donmuş bir pencere demektir. Bir formda ThenProc kullanın. Await'i konsol uygulamaları, servisler ve zaten bir arka plan iş parçacığında çalışan kod için saklayın. HTTP istemcisinin kendisi iş parçacığı güvenli değildir, dolayısıyla eşzamanlı her isteğe tek bir örneği görevler arasında paylaşmak yerine kendi örneğini verin.

Bir WebSocket istemcisini bağlamak

Bağlanmak eskiden Active := True ayarlamak ve işe yaradığını söylemesi için OnConnect olayını, ya da yaramadığını söylemesi için OnError olayını beklemek anlamına geliyordu. ConnectTask size aynı şeyi bir görev olarak verir.

uses
  sgcWebSocket, sgcBase_AsyncAwait;

var
  oTask: IsgcTask;
begin
  sgcWebSocketClient1.Host := 'echo.esegece.com';
  sgcWebSocketClient1.Port := 443;
  sgcWebSocketClient1.TLS  := True;

  oTask := sgcWebSocketClient1.ConnectTask(10000); // 10 second timeout

  oTask.ThenProc(
    procedure
    begin
      // main thread: the connection is established and ready to send
      sgcWebSocketClient1.WriteData('hello');
    end)
  .OnError(
    procedure(E: Exception)
    begin
      ShowMessage('Could not connect: ' + E.Message);
    end);
end;

Bağlantı girişimi bir arka plan iş parçacığında çalışır ve zaman aşımı gerçek bir zaman aşımıdır. Süresi dolarsa görev askıda kalmak yerine başarısız olur. Görevi iptal etmek soketi keser, bu da hâlâ ağı bekleyen bir bağlantı girişiminin blokajını kaldırır.

Beklemeden bir bağlantıyı beklemek: Connect ve LastError

Her şeyin bir göreve ihtiyacı yoktur. Bazen sadece bağlanan ve işe yarayıp yaramadığını size söyleyen bir satır kod istersiniz, ki çoğu kişinin en başında Active := True ifadesinden beklediği şey de budur. 2026.7, yalnızca bağlantı gerçekten kurulduğunda, sadece başlatıldığında değil, dönen bloklayıcı bir Connect ekliyor.

var
  vError: string;
begin
  // returns True only when the WebSocket connection is fully established
  if sgcWebSocketClient1.Connect(10000) then
    sgcWebSocketClient1.WriteData('hello')
  else
    // find out why, without wiring an OnError handler
    ShowMessage('Connect failed: ' + sgcWebSocketClient1.LastError);

  // or get the reason back directly from the call
  if not sgcWebSocketClient1.Connect(vError, 10000) then
    ShowMessage('Connect failed: ' + vError);

  // and a Disconnect that waits until the socket is really closed
  sgcWebSocketClient1.Disconnect(5000);
end;

Bu, hızlı yeniden bağlanmalar sırasında eskiden ısıran bir yarış durumunu kapatır. Connect yalnızca bağlantı tamamen kurulduğunda True döndürdüğü için, artık hiçbir şey yarı hazır bir bağlantı üzerinde çalışmaz ve Disconnect dönmeden önce soketin gerçekten kapanmasını bekler, dolayısıyla bir yeniden bağlanma döngüsü önceki bağlantıyla çakışamaz. LastError bileşen üzerinde bulunur ve son girişimin başarısız olma nedenini tutar, böylece bir bağlantı girişiminin neden başarısız olduğunu herhangi bir olaya abone olmadan öğrenebilirsiniz. Her bağlantı girişiminin başında otomatik olarak temizlenir ve ClearLastError onu elle sıfırlar.

Connect'in, tıpkı Await gibi çağıran iş parçacığını blokladığını unutmayın. Onu bir çalışan iş parçacığından veya bir konsol uygulamasından kullanın, formdan ise ConnectTask kullanın.

Bir AI sohbetini beklemek

AI istemcisi aynı deseni izler. ChatAsync, istemi bir arka plan iş parçacığında gönderir ve model yanıtına çözümlenen bir IsgcFuture<string> döndürür, dolayısıyla bir formdaki sohbet çağrısı artık üç satır ve hiç olay işleyicisi değildir.

uses
  sgcAI_Chat, sgcBase_AsyncAwait;

var
  oFuture: IsgcFuture<string>;
begin
  oFuture := sgcAIChat1.ChatAsync('Summarize this text in one sentence.');

  oFuture.ThenProc(
    procedure(const Value: string)
    begin
      // main thread: Value is the model answer
      MemoAnswer.Lines.Text := Value;
      ButtonSend.Enabled := True;
    end)
  .OnError(
    procedure(E: Exception)
    begin
      MemoAnswer.Lines.Text := 'Error: ' + E.Message;
      ButtonSend.Enabled := True;
    end);

  ButtonSend.Enabled := False; // the UI stays responsive while it runs
end;

O geleceği iptal etmek, devam eden isteği durdurur, dolayısıyla iletişim kutusunu kapatan veya Durdur'a basan bir kullanıcı, asla okumayacağı belirteçler için ödeme yapan bir LLM çağrısını arka planda çalışır durumda bırakmaz.

Bloklamak yerine zincirlemek (ThenProc / OnError)

Hangi iş parçacığının neyi çalıştırdığı konusunda açık olmakta fayda var, çünkü geri çağrı kodu genellikle orada yanlış gider. TsgcAsync.Run'a geçirdiğiniz iş ve GetFuture, ConnectTask veya ChatAsync arkasındaki istek, hepsi bir arka plan iş parçacığında çalışır. ThenProc ve OnError geri çağrıları ana iş parçacığına geri kuyruklanır, dolayısıyla içlerinde bir forma dokunmak güvenlidir.

İkisinden yalnızca biri tetiklenir. ThenProc işlem başarılı olduğunda çalışır, OnError bir istisna fırlattığında çalışır ve görev iptal edildiyse hiçbiri çalışmaz. Bu, sıralı durum için yeterlidir: bir sonraki çağrıyı önceki ThenProc içinden başlatın, zincir yürütüldüğü sırayla okunur. Zaten bir arka plan görevinin içindeyseniz ve yol boyunca arayüze dokunmanız gerekiyorsa, TsgcAsync.RunOnMainThread kullanın.

TsgcAsync.Run(
  procedure
  var
    oHTTP: TsgcHTTPClient;
    vBody: string;
  begin
    // background thread
    oHTTP := TsgcHTTPClient.Create(nil);
    try
      TsgcAsync.RunOnMainThread(
        procedure
        begin
          StatusBar1.SimpleText := 'Downloading...';
        end);

      vBody := oHTTP.Get('https://httpbin.org/get');

      TsgcAsync.RunOnMainThread(
        procedure
        begin
          // main thread again
          Memo1.Lines.Text := vBody;
          StatusBar1.SimpleText := 'Done';
        end);
    finally
      oHTTP.Free;
    end;
  end);

Gerçekten iptal eden iptal

Çoğu "iptal" uygulaması bir bayrak ayarlar ve sonuç nihayet geldiğinde onu yok sayar. Soket açık kalır, istek çalışmaya devam eder ve iş parçacığı meşgul kalır. sgcWebSockets'te her eşzamansız çağrı göreve bir iptal eylemi kaydeder, dolayısıyla Cancel alttaki işleme ulaşır: bir HTTP geleceğini iptal etmek isteği durdurur, bir ConnectTask görevini iptal etmek soketi keser ve bir ChatAsync çağrısını iptal etmek AI çağrısını durdurur. Bloklanmış çalışan iş parçacığı, bir zaman aşımı dolana kadar orada oturmak yerine uyanır.

Bu da bir zaman aşımını bir Delay görevi ile bir iptalden inşa etmeyi kolaylaştırır, ki bu kütüphanede örnek 18 olarak gelen desendir:

var
  oRequest: IsgcFuture<string>;
  oTimeout: IsgcTask;
begin
  oRequest := oHTTP.GetFuture('https://httpbin.org/delay/10');
  oTimeout := TsgcAsync.Delay(3000);

  // if the request answers first, drop the timeout
  oRequest.ThenProc(
    procedure(const Value: string)
    begin
      oTimeout.Cancel;
      Memo1.Lines.Text := Value;
    end);

  // if the timeout fires first, abort the request for real
  oTimeout.ThenProc(
    procedure
    begin
      if not oRequest.IsCompleted then
      begin
        oRequest.Cancel;
        ShowMessage('Request timed out');
      end;
    end);
end;

İptal edilmiş bir görev ne ThenProc ne de OnError tetikler. State değeri tsaCancelled olur, IsCancelled True döndürür ve Await içinde bloklanmış olan herkes bir EsgcTaskCancelled alır. Kendi görevinizi TsgcAsync.Run ile oluşturursanız ve bu görev durdurulabilir bir şeyi sarmalıyorsa, kendi durdurma işleminizi SetCancelProc ile kaydedin, Cancel onu çağıracaktır.

Delphi sürüm desteği

async/await birimi genel türler ve anonim yöntemler kullanır, dolayısıyla Delphi 2010 veya daha yenisini gerektirir. Delphi 7, 2007 ve 2009'da birim boş bir birim olarak derlenir ve eşzamansız yöntemler orada bulunmaz, dolayısıyla GetFuture, ConnectTask ve ChatAsync basitçe kullanılamaz. Kütüphanedeki diğer her şey, bu yöntemlerin yanında yer alan olay tabanlı API dâhil, Delphi 7'yi tam olarak eskisi gibi desteklemeye devam eder. Hiçbir şey kaldırılmadı ve mevcut hiçbir kodun davranışı değişmiyor, yeni çağrılar birer eklentidir.

Yukarıda anlatılan bloklayıcı Connect, Disconnect ve LastError genel tür kullanmaz, dolayısıyla desteklenen her Delphi sürümünde kullanılabilir.

Bütün bunlar için işlenmiş örnekler kaynakla birlikte sgcBase_AsyncAwait_Examples.pas içinde gelir, görevleri, gelecekleri, gecikmeleri, iptali, durumu yoklamayı, paralel HTTP isteklerini ve yukarıdaki zaman aşımı gözcüsünü kapsar.

Kullanılabilirlik

Görevler ve gelecekler, sgcWebSockets 2026.7'de Delphi 2010'dan Delphi 13'e kadar ve C++Builder'da, Win32/Win64, Linux64, macOS, Android ve iOS platformlarında kullanılabilir. Bloklayıcı Connect, Disconnect ve LastError ise Delphi 7'den 13'e kadar kullanılabilir.

Etkin aboneliği olan müşteriler yeni derlemeyi müşteri alanından indirebilir. Deneme kullanıcıları güncellenmiş kurulum dosyasını esegece.com/products/websockets/download adresinden alabilir.

Sorularınız, geri bildirimleriniz veya geri çağrı tabanlı bir formu görevlere taşımak için yardıma mı ihtiyacınız var? Bize ulaşın, kodu yazan kişilerden yanıt alacaksınız.