Search code examples
multithreadingdelphithread-synchronization

TQueue gets never emptied if Dequeue used as a procedure parameter


interface

type

TMulticastListenerThread = class(TThread)
strict private
  .....
  _queue: TQueue<string>;
  FOnReceive: TGetStrProc;
  procedure DoReceive();
  .....
public
  .....
  procedure Execute(); override;
  property OnReceive:TGetStrProc read FOnReceive write FOnReceive;
  .....
end;

implementation

procedure TMulticastListenerThread.Execute();
var addrlen: Integer;
    nbytes: Integer;
    msgbuf: array[1..MSGBUFSIZE] of AnsiChar;
    msgstr: AnsiString;
begin
  inherited;
  // now just enter a read-print loop
  while not Terminated do begin
    try
      if not _connected then begin
        Sleep(100);
        continue;
      end;
      addrlen := sizeof(addr);
      nbytes := recvfrom(fd, msgbuf, MSGBUFSIZE, 0, addr, addrlen);
      if (nbytes < 0) then begin
        perror('recvfrom');
      end
      else begin
        SetLength(msgstr, nbytes);
        Move(msgbuf, PAnsiChar(msgstr)^, nbytes);
        _queue.Enqueue(msgstr);
        Synchronize(DoReceive);
      end;
    except
      on ex: Exception do perror(ex.Message);
    end;
  end;
end;

Now all is about the synchronized DoReceive method.

If it contains such a code:

procedure TMulticastListenerThread.DoReceive();
begin
  while _queue.Count > 0 do
    if Assigned(FOnReceive) then FOnReceive(_queue.Dequeue());
end;

after I terminate my application it goes through the while loop again and again, _queue.Count is always 1, although no new strings are enqueued, and TMulticastListenerThread's destructor is never called.

When I dequeue through an intermediate local string variable:

procedure TMulticastListenerThread.DoReceive();
var s: string;
begin
  while _queue.Count > 0 do begin
    s := _queue.Dequeue();
    if Assigned(FOnReceive) then FOnReceive(s);
  end;
end;

application terminates normally and destructor is being called.

Could you explain this?


Solution

  • In following code _queue.Dequeue() will execute only if you have assigned FOnReceive event handler.

    procedure TMulticastListenerThread.DoReceive();
    begin
      while _queue.Count > 0 do
        if Assigned(FOnReceive) then FOnReceive(_queue.Dequeue());
    end;
    

    However this code will call _queue.Dequeue() every time, before your assignment check.

    procedure TMulticastListenerThread.DoReceive();
    var s: string;
    begin
      while _queue.Count > 0 do begin
        s := _queue.Dequeue();
        if Assigned(FOnReceive) then FOnReceive(s);
      end;
    end;