Do tej pory każde asynchroniczne wywołanie w sgcWebSockets oznaczało procedurę obsługi zdarzenia. Wywoływałeś Get w wątku w tle albo ustawiałeś Active := True i czekałeś na OnConnect. Wynik pojawiał się gdzie indziej, w innej metodzie, więc kod, który rozpoczynał operację, i kod, który konsumował odpowiedź, żyły w różnych miejscach. Dodaj drugie żądanie zależne od pierwszego, do tego obsługę błędów i panel "proszę czekać", który trzeba potem znowu ukryć, a skończysz z małą maszyną stanów rozsianą po całym formularzu: kilka prywatnych pól, żeby pamiętać, co robiłeś, flaga informująca, czy ostatnie wywołanie wciąż jest w toku, i procedura obsługi, która musi ustalić, które z trzech żądań właśnie wróciło.
sgcWebSockets 2026.7 dodaje API w stylu await, które usuwa większość tego balastu. Żądanie HTTP, połączenie WebSocket albo wywołanie czatu AI zwraca teraz obiekt, na który możesz poczekać, do którego możesz podpiąć wywołanie zwrotne albo który możesz anulować, dzięki czemu kod czyta się od góry do dołu, w kolejności, w jakiej się wykonuje. Anulowanie też nie jest tylko kosmetyką, przerywa operację leżącą pod spodem, a nie jedynie ignoruje odpowiedź, gdy ta w końcu nadejdzie.
Zadania i obiekty future
Nowy moduł to sgcBase_AsyncAwait. Wprowadza dwa interfejsy. IsgcTask reprezentuje pracę, która się kończy, ale nie zwraca wartości, a IsgcFuture<T> reprezentuje pracę zwracającą wartość typu T. Oba dają Ci te same cztery rzeczy: Await, aby blokować do zakończenia pracy, ThenProc, aby otrzymać wywołanie zwrotne po sukcesie, OnError, aby otrzymać wywołanie zwrotne przy wyjątku, oraz Cancel, aby przerwać pracę.
Własne zadania możesz zbudować za pomocą TsgcAsync.Run. Przekazana procedura lub funkcja wykonuje się w wątku w tle, a wywołania zwrotne wracają do wątku głównego, więc wewnątrz ThenProc i OnError można bezpiecznie operować na interfejsie użytkownika.
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;
Zadanie utrzymuje się przy życiu, dopóki działa, więc wywołanie typu "uruchom i zapomnij", jak to powyżej, jest w porządku. Jeśli chcesz je później sprawdzić, zachowaj interfejs w zmiennej i odczytuj IsCompleted, IsCancelled lub State, które przyjmuje jedną z wartości tsaPending, tsaRunning, tsaCompleted, tsaFailed lub tsaCancelled. Jest też TsgcAsync.Delay(aMilliseconds), które zwraca zadanie kończące się po upływie czasu i możliwe do anulowania, zanim się wykona, oraz TsgcAsync.RunOnMainThread, które przenosi fragment kodu z powrotem do wątku głównego z wnętrza zadania w tle.
Oczekiwanie na żądanie HTTP
Klient HTTP udostępnia obiekty future bezpośrednio, więc nie musisz sam opakowywać wywołania. GetFuture i PostFuture zwracają IsgcFuture<string>, które rozwiązuje się do treści odpowiedzi.
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;
Jeśli jesteś już w wątku roboczym albo piszesz aplikację konsolową, możesz po prostu zablokować się i odczytać wartość:
var
vBody: string;
begin
vBody := oHTTP.GetFuture('https://httpbin.org/get').Await;
WriteLn(vBody);
end;
Await ponownie zgłasza wyjątek, który wystąpił w wywołaniu w tle, opakowany w EsgcAsyncException niosący oryginalną nazwę klasy i komunikat, więc zwykłe try ... except wokół niego nadal działa. Jeśli obiekt future został anulowany, Await zgłasza zamiast tego EsgcTaskCancelled.
Jedno ostrzeżenie ważniejsze od wszystkich innych w tym wpisie. Wywołanie Await w wątku głównym blokuje wątek główny, co w aplikacji VCL lub FMX oznacza zamrożone okno aż do powrotu z wywołania. W formularzu używaj ThenProc. Await zachowaj dla aplikacji konsolowych, usług i kodu, który już działa w wątku w tle. Sam klient HTTP nie jest bezpieczny wątkowo, więc każdemu równoległemu żądaniu daj własną instancję zamiast współdzielić jedną między zadaniami.
Nawiązywanie połączenia klienta WebSocket
Nawiązanie połączenia oznaczało dawniej ustawienie Active := True i oczekiwanie, aż OnConnect powie Ci, że się udało, albo OnError powie, że nie. ConnectTask daje Ci to samo w postaci zadania.
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;
Próba połączenia wykonuje się w wątku w tle, a limit czasu jest prawdziwym limitem czasu. Kiedy upłynie, zadanie kończy się niepowodzeniem, zamiast zawiesić się na stałe. Anulowanie zadania rozłącza gniazdo, co odblokowuje połączenie wciąż czekające na sieć.
Czekanie na połączenie bez await: Connect i LastError
Nie wszystko potrzebuje zadania. Czasem chcesz po prostu linii kodu, która się połączy i powie Ci, czy się udało, czyli tego, czego większość ludzi od początku oczekiwała po Active := True. Wersja 2026.7 dodaje blokujące Connect, które wraca dopiero wtedy, gdy połączenie jest naprawdę nawiązane, a nie tylko rozpoczęte.
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;
To zamyka wyścig, który potrafił zaboleć przy szybkich ponownych połączeniach. Ponieważ Connect zwraca True dopiero wtedy, gdy połączenie jest w pełni gotowe, nic już nie działa na wpół gotowym połączeniu, a Disconnect czeka z powrotem, aż gniazdo naprawdę się zamknie, więc pętla ponownego łączenia nie może nałożyć się na poprzednie połączenie. LastError mieszka na komponencie i przechowuje powód niepowodzenia ostatniej próby, dzięki czemu możesz się dowiedzieć, dlaczego połączenie się nie udało, bez subskrybowania jakiegokolwiek zdarzenia. Jest czyszczone automatycznie na początku każdej próby połączenia, a ClearLastError resetuje je ręcznie.
Pamiętaj, że Connect blokuje wątek wywołujący, dokładnie tak samo jak Await. Używaj go z wątku roboczego lub aplikacji konsolowej, a w formularzu sięgaj po ConnectTask.
Oczekiwanie na czat AI
Klient AI działa według tego samego wzorca. ChatAsync wysyła prompt w wątku w tle i zwraca IsgcFuture<string>, które rozwiązuje się do odpowiedzi modelu, więc wywołanie czatu w formularzu to teraz trzy linie i zero procedur obsługi zdarzeń.
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;
Anulowanie tego obiektu future przerywa żądanie w locie, więc użytkownik, który zamknie okno dialogowe albo naciśnie Stop, nie zostawia w tle działającego wywołania LLM i nie płaci za tokeny, których nigdy nie przeczyta.
Łańcuchowanie zamiast blokowania (ThenProc / OnError)
Warto wyraźnie powiedzieć, który wątek co wykonuje, bo to właśnie tam kod z wywołaniami zwrotnymi zwykle się wykłada. Praca przekazana do TsgcAsync.Run oraz żądanie stojące za GetFuture, ConnectTask czy ChatAsync wykonują się w wątku w tle. Wywołania zwrotne ThenProc i OnError są kolejkowane z powrotem do wątku głównego, więc operowanie w nich na formularzu jest bezpieczne.
Uruchamia się tylko jedno z dwóch. ThenProc wykonuje się, gdy operacja się powiedzie, OnError wykonuje się, gdy zgłosi wyjątek, a żadne z nich nie wykonuje się, jeśli zadanie zostało anulowane. To wystarcza dla przypadku sekwencyjnego: uruchom kolejne wywołanie z wnętrza poprzedniego ThenProc, a łańcuch będzie czytany w kolejności wykonania. Jeśli jesteś już wewnątrz zadania w tle i musisz po drodze dotknąć interfejsu użytkownika, użyj 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);
Anulowanie, które naprawdę anuluje
Większość implementacji "anuluj" ustawia flagę i ignoruje wynik, gdy ten w końcu przyjdzie. Gniazdo pozostaje otwarte, żądanie wciąż działa, a wątek pozostaje zajęty. W sgcWebSockets każde asynchroniczne wywołanie rejestruje w zadaniu akcję anulowania, więc Cancel sięga do operacji pod spodem: anulowanie obiektu future HTTP przerywa żądanie, anulowanie ConnectTask rozłącza gniazdo, a anulowanie ChatAsync przerywa wywołanie AI. Zablokowany wątek roboczy budzi się, zamiast siedzieć bezczynnie do upływu limitu czasu.
Dzięki temu limit czasu łatwo zbudować z zadania Delay i anulowania, co jest wzorcem dostarczanym jako przykład 18 w bibliotece:
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;
Anulowane zadanie nie uruchamia ani ThenProc, ani OnError. Jego State przyjmuje wartość tsaCancelled, IsCancelled zwraca True, a każdy zablokowany w Await dostaje EsgcTaskCancelled. Jeśli budujesz własne zadanie za pomocą TsgcAsync.Run i opakowuje ono coś, co da się przerwać, zarejestruj własne przerwanie przez SetCancelProc, a Cancel je wywoła.
Obsługiwane wersje Delphi
Moduł async/await korzysta z typów generycznych i metod anonimowych, więc wymaga Delphi 2010 lub nowszego. W Delphi 7, 2007 i 2009 moduł kompiluje się do pustego modułu, a metod asynchronicznych po prostu nie ma, więc GetFuture, ConnectTask i ChatAsync nie są dostępne. Cała reszta biblioteki nadal wspiera Delphi 7 dokładnie tak jak wcześniej, łącznie z API opartym na zdarzeniach, obok którego stoją te metody. Nic nie zostało usunięte i żaden istniejący kod nie zmienia zachowania, nowe wywołania są dodatkiem.
Blokujące Connect, Disconnect i LastError opisane powyżej nie są generyczne, więc są dostępne w każdej obsługiwanej wersji Delphi.
Gotowe przykłady tego wszystkiego dostarczane są razem ze źródłami w sgcBase_AsyncAwait_Examples.pas i obejmują zadania, obiekty future, opóźnienia, anulowanie, odpytywanie stanu, równoległe żądania HTTP oraz powyższy watchdog limitu czasu.
Dostępność
Zadania i obiekty future są dostępne w sgcWebSockets 2026.7 od Delphi 2010 do Delphi 13 oraz w C++Builder, na Win32/Win64, Linux64, macOS, Android i iOS. Blokujące Connect, Disconnect i LastError są dostępne w Delphi od 7 do 13.
Klienci z aktywną subskrypcją mogą pobrać nową kompilację ze strefy klienta. Użytkownicy wersji próbnej mogą pobrać zaktualizowany instalator pod adresem esegece.com/products/websockets/download.
Pytania, uwagi albo pomoc w przeniesieniu formularza opartego na wywołaniach zwrotnych na zadania? Skontaktuj się z nami, odpowiedzą Ci osoby, które napisały ten kod.
