TsgcWebSocketClient › Events › OnQueueDrained
Fires when the outbound message queue goes from holding messages to being empty.
property OnQueueDrained: TsgcWSQueueDrainedEvent;
// TsgcWSQueueDrainedEvent = procedure(Connection: TsgcWSConnection) of object
—
Only fires when message queuing is active, that is when QueueOptions.Text.Level, QueueOptions.Binary.Level or QueueOptions.Ping.Level is set to something other than qmNone. Without a queue there is nothing to drain and the event is never raised in the default qdmTransition mode.
The handler runs inline on the connection thread and is deliberately not marshalled through NotifyEvents, unlike every other event of this component, so it must not touch the user interface. The deviation is intentional. The whole value of the event is that it runs at that exact point of the connection loop, right after the queue has drained and before the next read, while the socket is still idle, so the application can release the next chunk without waiting. Marshalling it to the main thread would destroy that. Any form, grid or label update has to be marshalled by the application itself, with TThread.Queue, TThread.Synchronize or a message posted to the main thread.
Exceptions raised inside the handler are caught and discarded so that the drain always completes. A handler that fails does so silently, so wrap the body in its own try..except block when a failure has to be reported.
The QueueOptions.DrainedMode property selects when the event is raised. With qdmTransition (default) it fires once per transition from non-empty to empty, as described below. With qdmEveryPass it fires on every pass of the connection loop, right after the queues are sent, even when the queue was already empty, and even when Text.Level, Binary.Level and Ping.Level are all qmNone, so no dummy queue level is needed. In this mode the handler is a per-connection hook on the connection thread, for example to send pending application data with WriteData so that every write to a connection happens from its own thread (with Binary.Level set to qmNone, WriteData inside the event writes directly to the socket). Like the transition mode it runs inline and does not follow NotifyEvents, so the handler must be fast and must not touch the user interface. Each connection copies the queue options when it connects, so set DrainedMode before activating the component, connections already open keep the previous mode.
In qdmTransition mode the event fires once per transition from non-empty to empty, not once per pass of the connection loop. It is not a signal that a burst has finished. If the drain keeps pace with a slow producer, every single message can raise its own event, because each message is written out before the next one is enqueued. What the event states is that the queue for this connection is empty right now, nothing more.
The typical use is flow control. When the client streams bulk data, use the event to release the next chunk so the producer follows the speed of the socket instead of filling memory. Pair it with the PendingCount property of TsgcWSConnection, which reports how many messages are still queued for the connection, to run a credit window instead of a stop and wait exchange.
The managed .NET port raises the same event with the same meaning. The one difference is how often it arrives. The Delphi drain runs once per pass of the connection loop, which is Options.ReadTimeOut milliseconds apart and 10 by default, so a slow trickle of messages is coalesced into few events. The managed drain is event driven and the same trickle can raise more events. Bursts behave identically on both.
procedure OnQueueDrained(Connection: TsgcWSConnection);
begin
// runs on the connection thread, do not touch the user interface here
Connection.WriteData(GetNextChunk);
end;
// qdmEveryPass: runs on every pass of the connection loop
oClient.QueueOptions.DrainedMode := qdmEveryPass;
oClient.QueueOptions.Ping.Level := qmLevel1;
procedure OnQueueDrained(Connection: TsgcWSConnection);
begin
// runs on the connection thread, do not touch the user interface here
if Connection.Disconnected then
exit;
if HasPendingData then
Connection.WriteData(GetPendingData);
end;