Jusqu'à présent, chaque appel asynchrone dans sgcWebSockets impliquait un gestionnaire d'événements. Vous appeliez Get sur un thread d'arrière-plan, ou vous définissiez Active := True et attendiez OnConnect. Le résultat arrivait ailleurs, dans une autre méthode, si bien que le code qui lançait l'opération et le code qui consommait la réponse vivaient à des endroits différents. Ajoutez une deuxième requête qui dépend de la première, plus la gestion des erreurs, plus un panneau « veuillez patienter » qu'il faut ensuite masquer, et vous vous retrouvez avec une petite machine à états éparpillée dans votre fiche : quelques champs privés pour vous rappeler ce que vous étiez en train de faire, un drapeau pour savoir si le dernier appel est toujours en cours, et un gestionnaire qui doit deviner laquelle des trois requêtes vient de revenir.
sgcWebSockets 2026.7 ajoute une API de style await qui supprime l'essentiel de tout cela. Une requête HTTP, une connexion WebSocket ou un appel de chat IA renvoient désormais un objet que vous pouvez attendre, auquel vous pouvez chaîner un rappel, ou que vous pouvez annuler, si bien que le code se lit de haut en bas dans l'ordre où il se produit. L'annulation n'est pas cosmétique non plus, elle interrompt l'opération sous-jacente au lieu de se contenter d'ignorer la réponse quand elle finit par arriver.
Tâches et futures
La nouvelle unité est sgcBase_AsyncAwait. Elle introduit deux interfaces. IsgcTask représente un travail qui se termine mais ne produit pas de valeur, et IsgcFuture<T> représente un travail qui produit une valeur de type T. Les deux vous offrent les quatre mêmes choses : Await pour bloquer jusqu'à ce que le travail soit terminé, ThenProc pour recevoir un rappel en cas de succès, OnError pour recevoir un rappel en cas d'exception, et Cancel pour l'arrêter.
Vous pouvez construire les vôtres avec TsgcAsync.Run. La procédure ou la fonction que vous passez s'exécute sur un thread d'arrière-plan, et les rappels reviennent sur le thread principal, il est donc sûr de toucher à l'interface utilisateur à l'intérieur de ThenProc et OnError.
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;
La tâche se maintient elle-même en vie pendant son exécution, un appel du type « lancer et oublier » comme celui ci-dessus est donc parfaitement valable. Si vous voulez l'inspecter plus tard, conservez l'interface dans une variable et lisez IsCompleted, IsCancelled ou State, qui vaut tsaPending, tsaRunning, tsaCompleted, tsaFailed ou tsaCancelled. Il existe aussi TsgcAsync.Delay(aMilliseconds), qui renvoie une tâche se terminant après un délai et pouvant être annulée avant son déclenchement, et TsgcAsync.RunOnMainThread, qui renvoie un morceau de code vers le thread principal depuis l'intérieur d'une tâche d'arrière-plan.
Attendre une requête HTTP
Le client HTTP expose directement des futures, vous n'avez donc pas à encapsuler l'appel vous-même. GetFuture et PostFuture renvoient un IsgcFuture<string> qui se résout en le corps de la réponse.
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;
Si vous êtes déjà sur un thread de travail, ou si vous écrivez une application console, vous pouvez simplement bloquer et lire la valeur :
var
vBody: string;
begin
vBody := oHTTP.GetFuture('https://httpbin.org/get').Await;
WriteLn(vBody);
end;
Await relève à nouveau l'exception levée par l'appel d'arrière-plan, enveloppée dans une EsgcAsyncException qui porte le nom de classe et le message d'origine, si bien qu'un simple try ... except autour fonctionne toujours. Si la future a été annulée, Await lève une EsgcTaskCancelled à la place.
Un avertissement qui compte plus que tout autre dans cet article. Appeler Await sur le thread principal bloque le thread principal, ce qui, dans une application VCL ou FMX, signifie une fenêtre gelée jusqu'au retour de l'appel. Dans une fiche, utilisez ThenProc. Réservez Await aux applications console, aux services, et au code qui s'exécute déjà sur un thread d'arrière-plan. Le client HTTP lui-même n'est pas thread-safe, donnez donc à chaque requête concurrente sa propre instance plutôt que d'en partager une entre plusieurs tâches.
Connecter un client WebSocket
Se connecter signifiait auparavant définir Active := True et attendre que OnConnect vous dise que cela avait fonctionné, ou que OnError vous dise le contraire. ConnectTask vous donne la même chose sous forme de tâche.
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;
La tentative de connexion s'exécute sur un thread d'arrière-plan et le délai d'attente est un vrai délai d'attente. S'il expire, la tâche échoue au lieu de rester suspendue. Annuler la tâche déconnecte la socket, ce qui débloque une connexion encore en attente sur le réseau.
Attendre une connexion sans await : Connect et LastError
Tout n'a pas besoin d'une tâche. Parfois vous voulez simplement une ligne de code qui se connecte et vous dit si cela a fonctionné, ce que la plupart des gens attendaient de Active := True au départ. 2026.7 ajoute un Connect bloquant qui ne revient que lorsque la connexion est réellement établie, et pas seulement lancée.
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;
Cela referme une situation de concurrence qui mordait lors des reconnexions rapides. Comme Connect ne renvoie True qu'une fois la connexion pleinement établie, plus rien ne s'exécute contre une connexion à moitié prête, et Disconnect attend que la socket soit réellement fermée avant de rendre la main, si bien qu'une boucle de reconnexion ne peut pas chevaucher la connexion précédente. LastError vit sur le composant et contient la raison de l'échec de la dernière tentative, vous pouvez donc découvrir pourquoi une connexion a échoué sans vous abonner à un quelconque événement. Elle est effacée automatiquement au début de chaque tentative de connexion, et ClearLastError la réinitialise à la main.
Notez que Connect bloque le thread appelant, exactement comme le fait Await. Utilisez-le depuis un thread de travail ou une application console, et utilisez ConnectTask depuis une fiche.
Attendre un chat IA
Le client IA suit le même schéma. ChatAsync envoie l'invite sur un thread d'arrière-plan et renvoie un IsgcFuture<string> qui se résout en la réponse du modèle, si bien qu'un appel de chat dans une fiche tient désormais en trois lignes et sans aucun gestionnaire d'événements.
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;
Annuler cette future interrompt la requête en cours, si bien qu'un utilisateur qui ferme la boîte de dialogue ou appuie sur Stop ne laisse pas un appel LLM tourner en arrière-plan et payer des jetons qu'il ne lira jamais.
Chaîner plutôt que bloquer (ThenProc / OnError)
Il vaut la peine d'être explicite sur le thread qui exécute quoi, car c'est là que le code de rappel se trompe habituellement. Le travail que vous passez à TsgcAsync.Run, et la requête derrière GetFuture, ConnectTask ou ChatAsync, s'exécutent tous sur un thread d'arrière-plan. Les rappels ThenProc et OnError sont remis en file sur le thread principal, il est donc sûr de toucher à une fiche à l'intérieur.
Un seul des deux se déclenche. ThenProc s'exécute lorsque l'opération réussit, OnError s'exécute lorsqu'elle lève une exception, et aucun des deux ne s'exécute si la tâche a été annulée. Cela suffit pour le cas séquentiel : lancez l'appel suivant depuis l'intérieur du ThenProc précédent et la chaîne se lit dans l'ordre où elle s'exécute. Si vous êtes déjà à l'intérieur d'une tâche d'arrière-plan et que vous devez toucher à l'interface utilisateur en cours de route, utilisez TsgcAsync.RunOnMainThread.
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);
Une annulation qui annule vraiment
La plupart des implémentations d'« annulation » lèvent un drapeau et ignorent le résultat lorsqu'il finit par arriver. La socket reste ouverte, la requête continue de tourner, et le thread reste occupé. Dans sgcWebSockets, chaque appel asynchrone enregistre une action d'annulation auprès de la tâche, si bien que Cancel atteint l'opération sous-jacente : annuler une future HTTP interrompt la requête, annuler un ConnectTask déconnecte la socket, et annuler un ChatAsync interrompt l'appel IA. Le thread de travail bloqué se réveille au lieu de rester là jusqu'à l'expiration d'un délai d'attente.
Cela rend facile la construction d'un délai d'attente à partir d'une tâche Delay et d'une annulation, ce qui est le motif livré comme exemple 18 dans la bibliothèque :
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;
Une tâche annulée ne déclenche ni ThenProc ni OnError. Son State devient tsaCancelled, IsCancelled renvoie True, et quiconque est bloqué dans Await reçoit une EsgcTaskCancelled. Si vous construisez votre propre tâche avec TsgcAsync.Run et qu'elle encapsule quelque chose d'interruptible, enregistrez votre propre interruption avec SetCancelProc et Cancel l'appellera.
Versions de Delphi prises en charge
L'unité async/await utilise les génériques et les méthodes anonymes, elle nécessite donc Delphi 2010 ou plus récent. Sur Delphi 7, 2007 et 2009, l'unité se compile en une unité vide et les méthodes asynchrones ne sont pas présentes, si bien que GetFuture, ConnectTask et ChatAsync ne sont tout simplement pas disponibles. Tout le reste de la bibliothèque prend toujours en charge Delphi 7 exactement comme avant, y compris l'API basée sur les événements à côté de laquelle ces méthodes se placent. Rien n'a été supprimé et aucun code existant ne change de comportement, les nouveaux appels sont des ajouts.
Les méthodes bloquantes Connect, Disconnect et la propriété LastError décrites plus haut ne sont pas génériques, elles sont donc disponibles sur toutes les versions de Delphi prises en charge.
Des exemples complets de tout cela sont livrés avec les sources dans sgcBase_AsyncAwait_Examples.pas, couvrant les tâches, les futures, les délais, l'annulation, l'interrogation de l'état, les requêtes HTTP parallèles et le chien de garde de délai d'attente ci-dessus.
Disponibilité
Les tâches et les futures sont disponibles dans sgcWebSockets 2026.7 de Delphi 2010 à Delphi 13 et sur C++Builder, sur Win32/Win64, Linux64, macOS, Android et iOS. Les méthodes bloquantes Connect, Disconnect et la propriété LastError sont disponibles de Delphi 7 à 13.
Les clients disposant d'un abonnement actif peuvent télécharger la nouvelle version depuis l'espace client. Les utilisateurs de la version d'essai peuvent récupérer l'installateur mis à jour sur esegece.com/products/websockets/download.
Des questions, des remarques, ou besoin d'aide pour porter une fiche basée sur les rappels vers les tâches ? Contactez-nous, vous recevrez une réponse des personnes qui ont écrit le code.
