Hasta ahora, cada llamada asíncrona en sgcWebSockets significaba un manejador de eventos. Llamabas a Get en un hilo secundario, o establecías Active := True y esperabas a OnConnect. El resultado llegaba en otra parte, en otro método, así que el código que iniciaba la operación y el código que consumía la respuesta vivían en lugares distintos. Añade una segunda petición que depende de la primera, más el manejo de errores, más un panel de "por favor espere" que hay que volver a ocultar, y acabas con una pequeña máquina de estados repartida por tu formulario: un par de campos privados para recordar lo que estabas haciendo, un indicador para saber si la última llamada sigue en curso, y un manejador que tiene que averiguar cuál de las tres peticiones acaba de volver.
sgcWebSockets 2026.7 añade una API de estilo await que elimina la mayor parte de eso. Una petición HTTP, una conexión WebSocket o una llamada de chat de IA devuelven ahora un objeto que puedes esperar, al que puedes encadenar una retrollamada, o que puedes cancelar, así que el código se lee de arriba abajo en el orden en que ocurre. La cancelación tampoco es cosmética, aborta la operación subyacente en lugar de limitarse a ignorar la respuesta cuando por fin llega.
Tareas y futuros
La nueva unidad es sgcBase_AsyncAwait. Introduce dos interfaces. IsgcTask representa un trabajo que termina pero no produce un valor, e IsgcFuture<T> representa un trabajo que produce un valor de tipo T. Ambos te dan las mismas cuatro cosas: Await para bloquear hasta que el trabajo esté hecho, ThenProc para recibir una retrollamada cuando tiene éxito, OnError para recibir una retrollamada cuando lanza una excepción, y Cancel para detenerlo.
Puedes construir el tuyo propio con TsgcAsync.Run. El procedimiento o la función que le pasas se ejecuta en un hilo secundario, y las retrollamadas vuelven en el hilo principal, así que es seguro tocar la interfaz de usuario dentro de ThenProc y 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 tarea se mantiene viva a sí misma mientras se ejecuta, así que una llamada del tipo lanzar y olvidar como la de arriba es correcta. Si quieres inspeccionarla más tarde, guarda la interfaz en una variable y lee IsCompleted, IsCancelled o State, que es uno de tsaPending, tsaRunning, tsaCompleted, tsaFailed o tsaCancelled. También existe TsgcAsync.Delay(aMilliseconds), que devuelve una tarea que se completa después de un tiempo de espera y que se puede cancelar antes de que se dispare, y TsgcAsync.RunOnMainThread, que devuelve un trozo de código al hilo principal desde dentro de una tarea en segundo plano.
Esperar una petición HTTP
El cliente HTTP expone futuros directamente, así que no tienes que envolver la llamada tú mismo. GetFuture y PostFuture devuelven un IsgcFuture<string> que se resuelve con el cuerpo de la respuesta.
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 ya estás en un hilo de trabajo, o estás escribiendo una aplicación de consola, puedes simplemente bloquear y leer el valor:
var
vBody: string;
begin
vBody := oHTTP.GetFuture('https://httpbin.org/get').Await;
WriteLn(vBody);
end;
Await vuelve a lanzar lo que sea que haya lanzado la llamada en segundo plano, envuelto en una EsgcAsyncException que lleva el nombre de la clase original y el mensaje, así que un simple try ... except alrededor sigue funcionando. Si el futuro fue cancelado, Await lanza EsgcTaskCancelled en su lugar.
Una advertencia que importa más que ninguna otra en este artículo. Llamar a Await en el hilo principal bloquea el hilo principal, lo que en una aplicación VCL o FMX significa una ventana congelada hasta que la llamada retorna. En un formulario, usa ThenProc. Reserva Await para aplicaciones de consola, servicios y código que ya se está ejecutando en un hilo secundario. El cliente HTTP en sí no es seguro para hilos, así que dale a cada petición concurrente su propia instancia en lugar de compartir una entre tareas.
Conectar un cliente WebSocket
Conectar solía significar establecer Active := True y esperar a que OnConnect te dijera que había funcionado, o a que OnError te dijera que no. ConnectTask te da lo mismo en forma de tarea.
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;
El intento de conexión se ejecuta en un hilo secundario y el tiempo de espera es un tiempo de espera real. Si expira, la tarea falla en vez de quedarse colgada. Cancelar la tarea desconecta el socket, lo que desbloquea una conexión que todavía está esperando a la red.
Esperar una conexión sin await: Connect y LastError
No todo necesita una tarea. A veces sólo quieres una línea de código que conecte y te diga si funcionó, que es lo que la mayoría de la gente esperaba que hiciera Active := True desde el principio. 2026.7 añade un Connect bloqueante que retorna sólo cuando la conexión está realmente establecida, no simplemente iniciada.
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;
Esto cierra una condición de carrera que solía morder durante las reconexiones rápidas. Como Connect sólo devuelve True una vez que la conexión está completamente establecida, ya nada se ejecuta contra una conexión a medio preparar, y Disconnect espera hasta que el socket está realmente cerrado antes de retornar, así que un bucle de reconexión no puede solaparse con la conexión anterior. LastError vive en el componente y contiene el motivo por el que falló el último intento, así que puedes averiguar por qué falló una conexión sin suscribirte a ningún evento. Se limpia automáticamente al comienzo de cada intento de conexión, y ClearLastError la reinicia a mano.
Ten en cuenta que Connect bloquea el hilo que lo llama, exactamente igual que hace Await. Úsalo desde un hilo de trabajo o una aplicación de consola, y usa ConnectTask desde un formulario.
Esperar un chat de IA
El cliente de IA sigue el mismo patrón. ChatAsync envía la petición en un hilo secundario y devuelve un IsgcFuture<string> que se resuelve con la respuesta del modelo, así que una llamada de chat en un formulario son ahora tres líneas y ningún manejador de eventos.
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;
Cancelar ese futuro aborta la petición en curso, así que un usuario que cierra el diálogo o pulsa Detener no deja una llamada a un LLM ejecutándose en segundo plano y pagando por tokens que nunca va a leer.
Encadenar en vez de bloquear (ThenProc / OnError)
Merece la pena ser explícito sobre qué hilo ejecuta qué, porque ahí es donde el código de retrollamada suele fallar. El trabajo que le pasas a TsgcAsync.Run, y la petición que hay detrás de GetFuture, ConnectTask o ChatAsync, se ejecutan todos en un hilo secundario. Las retrollamadas ThenProc y OnError se encolan de vuelta al hilo principal, así que tocar un formulario dentro de ellas es seguro.
Sólo se dispara una de las dos. ThenProc se ejecuta cuando la operación tiene éxito, OnError se ejecuta cuando lanza una excepción, y ninguna de las dos se ejecuta si la tarea fue cancelada. Eso basta para el caso secuencial: inicia la siguiente llamada desde dentro del ThenProc anterior y la cadena se lee en el orden en que se ejecuta. Si ya estás dentro de una tarea en segundo plano y necesitas tocar la interfaz a mitad de camino, usa 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);
Una cancelación que cancela de verdad
La mayoría de las implementaciones de "cancelar" activan un indicador e ignoran el resultado cuando finalmente llega. El socket sigue abierto, la petición sigue ejecutándose y el hilo sigue ocupado. En sgcWebSockets cada llamada asíncrona registra una acción de cancelación en la tarea, así que Cancel llega hasta la operación que hay debajo: cancelar un futuro HTTP aborta la petición, cancelar un ConnectTask desconecta el socket, y cancelar un ChatAsync aborta la llamada de IA. El hilo de trabajo bloqueado se despierta en vez de quedarse ahí hasta que expire un tiempo de espera.
Eso hace que sea fácil construir un tiempo de espera a partir de una tarea Delay y una cancelación, que es el patrón que se incluye como ejemplo 18 en la librería:
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;
Una tarea cancelada no dispara ni ThenProc ni OnError. Su State pasa a ser tsaCancelled, IsCancelled devuelve True, y cualquiera bloqueado en Await recibe una EsgcTaskCancelled. Si construyes tu propia tarea con TsgcAsync.Run y envuelve algo abortable, registra tu propia acción de aborto con SetCancelProc y Cancel la invocará.
Versiones de Delphi soportadas
La unidad async/await usa genéricos y métodos anónimos, así que necesita Delphi 2010 o superior. En Delphi 7, 2007 y 2009 la unidad compila como una unidad vacía y los métodos asíncronos no están ahí, así que GetFuture, ConnectTask y ChatAsync simplemente no están disponibles. Todo lo demás en la librería sigue soportando Delphi 7 exactamente como antes, incluida la API basada en eventos junto a la que se sitúan estos métodos. No se ha eliminado nada y ningún código existente cambia de comportamiento, las nuevas llamadas son añadidos.
El Connect bloqueante, Disconnect y LastError descritos arriba no son genéricos, así que están disponibles en todas las versiones de Delphi soportadas.
Los ejemplos completos de todo esto se incluyen con el código fuente en sgcBase_AsyncAwait_Examples.pas, y cubren tareas, futuros, retardos, cancelación, consulta del estado, peticiones HTTP en paralelo y el vigilante de tiempo de espera de arriba.
Disponibilidad
Las tareas y los futuros están disponibles en sgcWebSockets 2026.7 desde Delphi 2010 hasta Delphi 13 y en C++Builder, en Win32/Win64, Linux64, macOS, Android e iOS. El Connect bloqueante, Disconnect y LastError están disponibles en Delphi 7 hasta 13.
Los clientes con una suscripción activa pueden descargar la nueva compilación desde el área de clientes. Los usuarios de la versión de prueba pueden obtener el instalador actualizado en esegece.com/products/websockets/download.
¿Preguntas, comentarios o ayuda para migrar un formulario basado en retrollamadas a tareas? Ponte en contacto, recibirás respuesta de las personas que escribieron el código.
