* * *

Author Topic: Multithreading with Synaser/Synapse  (Read 2691 times)

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Multithreading with Synaser/Synapse
« on: April 21, 2017, 01:06:19 pm »
Hello,

in my programm I use Synaser for serial communication and Synapse for UDP. For receiving, each port has its own thread polling BlockSerial.RecvByte (UDPBlockSocket.RecvPacket respectively). Sending is always done via main thread. In Synaser I experienced crashes, probably when the polling thread and the main thread access the BlockSerial simultaniously (but not to sure about that). Thus I added some blocking mechanism with boolean variables. I thought boolean variables are thread safe, but it seems that they are not suitable for interlocks. Using critical sections I'd like to avoid, as for each single poll the critical section has to be entered end left again (performance). An other possibility would be doing the sending from within the polling thread. But this would need an additional buffer for inter-thread communication.

So how is the correct way to use the Synaser and Synapse in Multithreading applications?

Or could there be an other issue, e.g. that I have to check if transmitting is already finished, before I poll the serial port?

Thanks!
« Last Edit: April 21, 2017, 01:07:54 pm by kupferstecher »

Blestan

  • Sr. Member
  • ****
  • Posts: 451
Re: Multithreading with Synaser/Synapse
« Reply #1 on: April 21, 2017, 02:32:59 pm »
you must use interlocked compare exchange and a spinwait in the threads to avoid using critical sections... and you must use a queue to post packets to the serial post no trying to write to the COM when the thread want but to push data to the queue and the main thread just to poll if data ca be writen to com and pull from the queue ...
Speak postscript or die!
Translate to pdf and live!

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Re: Multithreading with Synaser/Synapse
« Reply #2 on: April 21, 2017, 08:59:46 pm »
Thanks for the reply! InterlockedCompareExchange I've never used before, so I first had to do some reading to understand your comment.

Would the below structure work? This would even avoid a queue.

Another question, if I have two COM ports to listen to, then I use two BlockSerial and two polling threads. can the two threads independetly poll their own BlockSerial or is there also synchronisation necessary? (As I encounter sporadic crashes I couldn't just test it).


Code: Pascal  [Select]
  1. const
  2.   ComAccessIdle = 0;
  3.   ComAccessRead = 1;
  4.   ComAccessWrite = 2;
  5.  
  6.   Type TReceiveThread = class(TThread)
  7.     //...
  8.     ComAccess: Integer;  
  9.   end;
  10.  
  11.  
  12. //---------------------------------------------------------  
  13. // Attention: Called by main thread
  14. Procedure TReceiveThread.Send(OutValue:Byte);
  15. var CurrentComAccess: Integer;
  16. begin
  17.   While not Terminated do begin
  18.     CurrentComAccess:= InterlockedCompareExchange(ComAccess,ComAccessWrite,ComAccessIdle);
  19.     if CurrentComAccess = ComAccessIdle then BREAK;
  20.   End;//while
  21.  
  22.   BlockSerial.SendByte(OutValue);
  23.  
  24.   InterlockedExchange(ComAccess,ComAccessIdle);
  25.  
  26. end;
  27.  
  28. //---------------------------------------------------------
  29. Procedure TReceiveThread.Execute;
  30. var CurrentComAccess, LastErr: Integer;
  31. begin
  32.  
  33.   While not Terminated do begin
  34.     CurrentComAccess:= InterlockedCompareExchange(ComAccess,ComAccessRead,ComAccessIdle);
  35.     if CurrentComAccess <> ComAccessIdle
  36.     then begin spinwait(10); CONTINUE; end;  
  37.  
  38.     InValue := BlockSerial.RecvByte(10);
  39.     LastErr := BlockSerial.LastError;
  40.    
  41.     InterlockedExchange(ComAccess,ComAccessIdle);
  42.    
  43.    
  44.     If LastErr <> 0 Then Begin
  45.       Sleep(10);
  46.       CONTINUE;
  47.     End;
  48.      
  49.     //Process read value
  50.     //...
  51.  
  52.   End;//While
  53.  
  54. end;


Blestan

  • Sr. Member
  • ****
  • Posts: 451
Re: Multithreading with Synaser/Synapse
« Reply #3 on: April 22, 2017, 08:10:00 am »
look you must understand atomic operation very well before you can use them ... i propose you to stick with critial sections and make code work and try to make it lock free... the best scenario is the "share nothing"  model - one socket and one com per thread ...
p.s  interlockcompare exchange is pointless on local (stack) variables ... each thread has its own current com access var. use global. second. :
var threadusingcom: tthreadhandle=0; // global

try // avoid deadlocks
 while not icx ( threadusingcom, myId, 0)<>,0 do spinwait;
send_very_quickly(somedata);
finaly
icx(thethreadusingcom,0,myId);
« Last Edit: April 22, 2017, 08:25:36 am by Blestan »
Speak postscript or die!
Translate to pdf and live!

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Re: Multithreading with Synaser/Synapse
« Reply #4 on: April 22, 2017, 11:02:06 am »
look you must understand atomic operation very well before you can use them ...
Thats why I ask here.  :)
Quote
p.s  interlockcompare exchange is pointless on local (stack) variables ...
each thread has its own current com access var. use global.
In my code CurrentComAccess is just the (temp) result of icx, not the shared variable. The shared variable (private ComAccess: Integer;) is defined as variable of the thread class, is this 'global' enough?

Quote
try // avoid deadlocks
 while not icx ( threadusingcom, myId, 0)<>,0 do spinwait;
send_very_quickly(somedata);
finaly
icx(thethreadusingcom,0,myId);
I don't see the big difference here to the version I postet. The try-finally and using thread IDs are good hints though. Thanks for your help!

Blestan

  • Sr. Member
  • ****
  • Posts: 451
Re: Multithreading with Synaser/Synapse
« Reply #5 on: April 28, 2017, 03:08:42 pm »
Quote
In my code CurrentComAccess is just the (temp) result of icx, not the shared variable. The shared variable (private ComAccess: Integer;) is defined as variable of the thread class, is this 'global' enough?
no its not global at all  every thread instance will have it own variable and you cannot do sync like that :))  at least define it as class var :)))
Speak postscript or die!
Translate to pdf and live!

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Re: Multithreading with Synaser/Synapse
« Reply #6 on: April 28, 2017, 11:16:00 pm »
every thread instance will have it own variable
Its intentionally and hopefully works :-)
I did some effort and  draw an overview of my programm structure regarding the threads. You can see through the non-class-variable each COM-port has its own locking mechanism. If I understood you correct, then two prorts don't need to consider each other. So only locking against the sending thread is required.

In Terminal.Decoder and Terminal.Transmitter I currently use Critical Sections to do locking. I still don't understand in which case the CriticalSection is required and when a icx-locking mechanism is enough. Within my queues* I also use critical sections. When using icx instead, is it enough to do blocking in the same way as in the COM-Threads, or has the icx-operation to be performed on the queue variables?

*Queue is implemented as array, not as linked list.

Regards

avra

  • Hero Member
  • *****
  • Posts: 1063
    • Additional info
Re: Multithreading with Synaser/Synapse
« Reply #7 on: April 29, 2017, 12:46:35 am »
in my programm I use Synaser for serial communication and Synapse for UDP. For receiving, each port has its own thread polling BlockSerial.RecvByte (UDPBlockSocket.RecvPacket respectively). Sending is always done via main thread. In Synaser I experienced crashes, probably when the polling thread and the main thread access the BlockSerial simultaniously (but not to sure about that). Thus I added some blocking mechanism with boolean variables. I thought boolean variables are thread safe, but it seems that they are not suitable for interlocks. Using critical sections I'd like to avoid, as for each single poll the critical section has to be entered end left again (performance).
I also thought there are shortcuts that I can get away with, but found out the opposite the hard way:
http://forum.lazarus.freepascal.org/index.php/topic,33597.msg217968.html
The cure is proper multithreading.
ct2laz - Easily convert components and projects between Lazarus and CodeTyphon

serbod

  • Full Member
  • ***
  • Posts: 108
Re: Multithreading with Synaser/Synapse
« Reply #8 on: April 29, 2017, 09:58:54 am »
It's very simple and robust. You only need follow some rules:
- don't use Synchronize()
- keep TThread.Create() and TThread.Destroy() empty.
- don't allow public methods, only variables
- put all communication initialization/finalization in TThread.Execute() method.
- perform control and data exchange via shared variables and thread-safe objects.

TCriticalSection is most simple way to implement thread-safe for shared objects. Note, that TThreads is NOT shared objects! You can share messages queue or data buffers between threads. But creating locking mechanisms inside TThread cause troubles.

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Re: Multithreading with Synaser/Synapse
« Reply #9 on: April 29, 2017, 10:33:42 am »
but found out the opposite the hard way:
Yes, multithreading looks so easy using the TThread class. But doing the inter-thread communication correctly needs deeper understanding and/or discipline. I still didn't figure out how an locking mechanism with boolean variables could fail, but I observed it does, at least in the way I implemented it.

I still hope for some more inputs on that topic, especially when Critical Sections are required and when a locking mechanism using InterlockCompareExchange is enough. When using CriticalSection I felt having a performance drop.

It's very simple and robust. You only need follow some rules:
I believe these rules make it work fine, but may be to limited in some szenarios. E.g. "don't allow public methods, only variables", in general yes, but when doing proper blocking than this should work, doesn't? About the variables, are strings thread safe? I'd think not.

Quote
TCriticalSection is most simple way to implement thread-safe for shared objects.
I "feel" performace issues, so trying to use other techniques.

Quote
Note, that TThreads is NOT shared objects!
What do you mean by that? Sure, no other thread may enter/run the Execute procedure. But there could be even several instances for one Thread-class. In my program this is the case. The number of COM ports is user choice. Sended commands are passed to all ports by the TransmitterThread. Thats why I don't want to use a queue for each Com-Port, because they each receive the same data.

Quote
But creating locking mechanisms inside TThread cause troubles.
Thats what I experienced  :)
But should be working though?

Regards

serbod

  • Full Member
  • ***
  • Posts: 108
Re: Multithreading with Synaser/Synapse
« Reply #10 on: April 29, 2017, 11:34:11 am »
Quote
I believe these rules make it work fine, but may be to limited in some szenarios.

If you want simple and robust solution, then adapt scenario to rules. =)

General idea, that all operations with communication port (open, control, read, write, close) executed in same context and thread. Technically, it possible, that some communication operations commited in main thread, and some in separate threads, and even in separate modules (DLLs). But locking and memory sharing mechanisms is context-dependent, they can deadlock inside thread or DLL initialization/finalization. So, best way to win this fight - is avoid it. =)

Machine word based variables (Integer, Boolean, Pointer, etc) is thread-safe, but not atomic, and not applicable to shared lock implementation. CriticalSection is very fast and good enough for slow and buffered communications (COM ports, IP). If you experience performanche issues, that must be something else.

Code: Pascal  [Select]
  1. type
  2.   TSynUdpSockThread = class(TThread)
  3.   private
  4.     FLastError: Integer;
  5.     FLastErrorText: string;
  6.     FLocalIP: string;
  7.     FLocalPort: Integer;
  8.     FSockHandle: Integer;
  9.     FBlockSocket: TBlockSocket;
  10.   protected
  11.     function IsConnected(): Boolean;
  12.     function ReadPacketFromSynSocket(var APacket: TUdpPacketRec): Boolean;
  13.     function WritePacketToSynSocket(const APacket: TUdpPacketRec): Boolean;
  14.     procedure Execute(); override;
  15.   public
  16.     TxPktQueue: TUdpPacketQueue;
  17.     RxPktQueue: TUdpPacketQueue;
  18.     { Thread suspended after Create.
  19.       After assigning TxPktQueue and RxPktQueue set Suspended := False }
  20.     constructor Create(const ALocalIP: string; ALocalPort: Integer);
  21.     destructor Destroy(); override;
  22.     { 0 - no errors }
  23.     property LastError: Integer read FLastError;
  24.     { Last error text description }
  25.     property LastErrorText: string read FLastErrorText;
  26.     property LocalIP: string read FLocalIP;
  27.     property LocalPort: Integer read FLocalPort;
  28.     property Socket: TBlockSocket read FBlockSocket;
  29.   end;
  30.  
  31.  
  32. constructor TSynUdpSockThread.Create(const ALocalIP: string; ALocalPort: Integer);
  33. begin
  34.   inherited Create(True);
  35.   FLocalIP := ALocalIP;
  36.   FLocalPort := ALocalPort;
  37.   FSockHandle := 0;
  38.   FLastError := 0;
  39.   FLastErrorText := '';
  40.   TxPktQueue := nil;
  41.   RxPktQueue := nil;
  42. end;
  43.  
  44. destructor TSynUdpSockThread.Destroy();
  45. begin
  46.   TxPktQueue := nil;
  47.   RxPktQueue := nil;
  48.  
  49.   inherited;
  50. end;
  51.  
  52. function TSynUdpSockThread.IsConnected(): Boolean;
  53. begin
  54.   Result := ((Socket.LastError = 0) or (Socket.LastError = WSAETIMEDOUT));
  55. end;
  56.  
  57. procedure TSynUdpSockThread.Execute();
  58. var
  59.   RxPacket: TUdpPacketRec;
  60.   TxPacket: TUdpPacketRec;
  61.   NeedSleep: Boolean;
  62. begin
  63.   FBlockSocket := TUDPBlockSocket.Create();
  64.   try
  65.     try
  66.       // bind to local addr:port
  67.       Socket.Bind(FLocalIP, IntToStr(FLocalPort));
  68.       if Socket.LastError <> 0 then
  69.       begin
  70.         FLastErrorText := Socket.LastErrorDesc;
  71.         Terminate();
  72.       end;
  73.     except
  74.       on E: Exception do
  75.       begin
  76.         FLastErrorText := E.Message;
  77.         Terminate();
  78.       end;
  79.     end;
  80.  
  81.     try
  82.       while (not Terminated) and IsConnected() do
  83.       begin
  84.         NeedSleep := True;
  85.  
  86.         begin
  87.           // read
  88.           while IsConnected() and ReadPacketFromSynSocket(RxPacket) do
  89.           begin
  90.             if Assigned(RxPktQueue) then
  91.               RxPktQueue.Push(RxPacket);
  92.             NeedSleep := False;
  93.           end;
  94.         end;
  95.  
  96.         //if IsConnected() and Socket.CanWrite(WAIT_DATA_TIMEOUT) then
  97.         begin
  98.           // write
  99.           while Assigned(TxPktQueue) and IsConnected() and TxPktQueue.Pull(TxPacket) do
  100.           begin
  101.             WritePacketToSynSocket(TxPacket);
  102.             NeedSleep := False;
  103.           end;
  104.         end;
  105.         if not IsConnected() then
  106.         begin
  107.           Terminate();
  108.         end;
  109.  
  110.         if NeedSleep then
  111.         begin
  112.           Sleep(1);
  113.         end;
  114.       end;
  115.     except
  116.       on E: Exception do
  117.       begin
  118.         FLastErrorText := E.Message;
  119.         Terminate();
  120.       end;
  121.     end;
  122.  
  123.     if (Socket.LastError <> 0) then
  124.     begin
  125.       FLastErrorText := Socket.LastErrorDesc;
  126.     end;
  127.  
  128.   finally
  129.     FreeAndNil(FBlockSocket);
  130.   end;
  131. end;
  132.  
  133. function TSynUdpSockThread.WritePacketToSynSocket(
  134.   const APacket: TUdpPacketRec): Boolean;
  135. begin
  136.   FBlockSocket.SetRemoteSin(APacket.PeerIP, IntToStr(APacket.PeerPort));
  137.   FBlockSocket.SendString(APacket.Data);
  138.   FLastError := FBlockSocket.LastError;
  139.   Result := (FLastError = 0);
  140. end;
  141.  
  142. function TSynUdpSockThread.ReadPacketFromSynSocket(
  143.   var APacket: TUdpPacketRec): Boolean;
  144. begin
  145.   APacket.Data := FBlockSocket.RecvPacket(10);
  146.   Result := IsConnected and (Length(APacket.Data) > 0);
  147.   if Result then
  148.   begin
  149.     APacket.LocalIP := FLocalIP;
  150.     APacket.LocalPort := FLocalPort;
  151.     APacket.PeerIP := FBlockSocket.GetRemoteSinIP();
  152.     APacket.PeerPort := FBlockSocket.GetRemoteSinPort();
  153.   end;
  154. end;
  155.  
  156.  
  157.  

kupferstecher

  • Jr. Member
  • **
  • Posts: 96
Re: Multithreading with Synaser/Synapse
« Reply #11 on: April 29, 2017, 10:22:37 pm »
Serbod, thanks for the example code. I'm interested in your TUdpPacketQueue as I did a queue implementation myself and would like to see some comparison :)

Machine word based variables (Integer, Boolean, Pointer, etc) is thread-safe, but not atomic, and not applicable to shared lock implementation.
How about type real? If I write into a variable with one thread and read by another thread, may there be wrong values? If its still the old value then its no problem, but shouldn't be half-updated...

Paul Breneman

  • Full Member
  • ***
  • Posts: 242
    • Control Pascal
Re: Multithreading with Synaser/Synapse
« Reply #12 on: April 30, 2017, 04:25:17 am »
This interesting message thread caused me to post a possible contract job here: http://forum.lazarus.freepascal.org/index.php/topic,36705.0.html
Regards,
Paul Breneman
www.ControlPascal.com

serbod

  • Full Member
  • ***
  • Posts: 108
Re: Multithreading with Synaser/Synapse
« Reply #13 on: May 02, 2017, 09:34:47 am »
Serbod, thanks for the example code. I'm interested in your TUdpPacketQueue as I did a queue implementation myself and would like to see some comparison :)

In my case, it's smart queue with replaceable items (to decrease heap memory manager usage), some QoS, filtering, statistic and monitoring. TUdpPacketRec record size was fixed, with bytes array. But, actually, that not necessary, it increase stability on low performance and low memory environments.

Code: Pascal  [Select]
  1. unit UdpQueue;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, SyncObjs, SysUtils;
  7.  
  8. type
  9.   { Safe UDP packet size - 512 bytes. }
  10.   TUdpPacketRec = record
  11.     LocalPort: Integer;
  12.     PeerPort: Integer;
  13.     Priority: Byte;
  14.     LocalIP: AnsiString;
  15.     PeerIP: AnsiString;
  16.     Channel: AnsiString;
  17.     Data: AnsiString;
  18.   end;
  19.  
  20.   TUdpPacketEvent = procedure(Sender: TObject; const UdpPacketRec: TUdpPacketRec) of object;
  21.  
  22.   TUdpPacketQueueItem = class(TObject)
  23.   public
  24.     UdpPacketRec: TUdpPacketRec;
  25.   end;
  26.  
  27.   // thread-safe packets queue with reusable items
  28.   TUdpPacketQueue = class(TObject)
  29.   private
  30.     FLock: TCriticalSection;
  31.     FList: TList;
  32.     FCapacity: Integer;
  33.     FCount: Integer;
  34.     function GetCapacity: Integer;
  35.     function GetCount: Integer;
  36.     procedure SetCapacity(const Value: Integer);
  37.   public
  38.     constructor Create(ACapacity: Integer);
  39.     destructor Destroy(); override;
  40.     {Add packet to queue, increase queue length. }
  41.     function Push(const APacket: TUdpPacketRec): Boolean;
  42.     { Get packet from queue, decrease queue length. Returns False if queue empty }
  43.     function Pull(out APacket: TUdpPacketRec): Boolean;
  44.     { Read packet from queue without decreasing queue length. Returns False if queue empty }
  45.     function Peek(out APacket: TUdpPacketRec): Boolean;
  46.     { Remove all queue items and set queue length to 0 }
  47.     procedure Clear();
  48.     { Minimal length of internal list, count of replaceable items
  49.       Queue can grow over capacity, but internal list can't be less, than capacity }
  50.     property Capacity: Integer read GetCapacity write SetCapacity;
  51.     { Length of queue }
  52.     property Count: Integer read GetCount;
  53.   end;
  54.  
  55.  
  56. implementation
  57.  
  58. { TUdpPacketQueue }
  59.  
  60. constructor TUdpPacketQueue.Create(ACapacity: Integer);
  61. begin
  62.   inherited Create();
  63.   FLock := TCriticalSection.Create();
  64.   FList := TList.Create();
  65.   FCapacity := 0;
  66.   FCount := 0;
  67.   SetCapacity(ACapacity);
  68. end;
  69.  
  70. destructor TUdpPacketQueue.Destroy;
  71. begin
  72.   SetCapacity(0);
  73.   FreeAndNil(FList);
  74.   FreeAndNil(FLock);
  75.   inherited;
  76. end;
  77.  
  78. procedure TUdpPacketQueue.Clear();
  79. var
  80.   Item: TUdpPacketQueueItem;
  81. begin
  82.   if Assigned(FLock) then FLock.Acquire();
  83.   try
  84.     while FList.Count > 0 do
  85.     begin
  86.       Item := TUdpPacketQueueItem(FList.Items[0]);
  87.       Item.Free();
  88.       FList.Delete(0);
  89.     end;
  90.     FList.Clear();
  91.     FCount := 0;
  92.   finally
  93.     if Assigned(FLock) then FLock.Release();
  94.   end;
  95. end;
  96.  
  97. function TUdpPacketQueue.Pull(out APacket: TUdpPacketRec): Boolean;
  98. var
  99.   Item: TUdpPacketQueueItem;
  100.   i: Integer;
  101. begin
  102.   Result := False;
  103.   if (Count <= 0) then Exit;
  104.  
  105.   FLock.Acquire();
  106.   try
  107.     if (Count > 0) then
  108.     begin
  109.       Item := TUdpPacketQueueItem(FList.Items[0]);
  110.       APacket := Item.UdpPacketRec;
  111.       // shift all items to position of zero item
  112.       for i := 0 to FCount-2 do
  113.       begin
  114.         FList.Exchange(i, i+1);
  115.       end;
  116.       if FList.Count > FCapacity then
  117.       begin
  118.         // list size more than capacity, remove last item
  119.         Item := TUdpPacketQueueItem(FList.Items[FList.Count-1]);
  120.         Item.Free();
  121.         FList.Delete(FList.Count-1);
  122.       end;
  123.       Dec(FCount);
  124.       Result := True;
  125.     end;
  126.   finally
  127.     FLock.Release();
  128.   end;
  129. end;
  130.  
  131. function TUdpPacketQueue.Peek(out APacket: TUdpPacketRec): Boolean;
  132. var
  133.   Item: TUdpPacketQueueItem;
  134. begin
  135.   Result := False;
  136.   if (Count <= 0) then Exit;
  137.  
  138.   FLock.Acquire();
  139.   try
  140.     if (Count > 0) then
  141.     begin
  142.       Item := TUdpPacketQueueItem(FList.Items[0]);
  143.       APacket := Item.UdpPacketRec;
  144.       Result := True;
  145.     end;
  146.   finally
  147.     FLock.Release();
  148.   end;
  149. end;
  150.  
  151. function TUdpPacketQueue.Push(const APacket: TUdpPacketRec): Boolean;
  152. var
  153.   Item: TUdpPacketQueueItem;
  154. begin
  155.   if Assigned(FLock) then
  156.   begin
  157.     FLock.Acquire();
  158.     try
  159.       if FCount < FList.Count then
  160.       begin
  161.         // put data to item at queue length position
  162.         Item := TUdpPacketQueueItem(FList.Items[FCount]);
  163.       end
  164.       else
  165.       begin
  166.         // queue length less than list size, add new list item
  167.         Item := TUdpPacketQueueItem.Create();
  168.         FList.Add(Item);
  169.       end;
  170.       Item.UdpPacketRec := APacket;
  171.       Inc(FCount);
  172.     finally
  173.       FLock.Release();
  174.     end;
  175.     Result := True;
  176.   end
  177.   else
  178.     Result := False;
  179. end;
  180.  
  181. function TUdpPacketQueue.GetCapacity: Integer;
  182. begin
  183.   Result := FCapacity;
  184. end;
  185.  
  186. procedure TUdpPacketQueue.SetCapacity(const Value: Integer);
  187. var
  188.   Item: TUdpPacketQueueItem;
  189. begin
  190.   FLock.Acquire();
  191.   try
  192.     FCapacity := Value;
  193.     // add replaceable list items
  194.     while FList.Count < FCapacity do
  195.     begin
  196.       Item := TUdpPacketQueueItem.Create();
  197.       FList.Add(Item);
  198.     end;
  199.  
  200.     while FList.Count > FCapacity do
  201.     begin
  202.       // list size is more, than desired capacity, remove last list item
  203.       Item := TUdpPacketQueueItem(FList.Items[FList.Count-1]);
  204.       Item.Free();
  205.       FList.Delete(FList.Count-1);
  206.     end;
  207.   finally
  208.     FLock.Release();
  209.   end;
  210. end;
  211.  
  212. function TUdpPacketQueue.GetCount: Integer;
  213. begin
  214.   Result := FCount;
  215. end;
  216.  
  217. end.

Thaddy

  • Hero Member
  • *****
  • Posts: 4233
Re: Multithreading with Synaser/Synapse
« Reply #14 on: May 02, 2017, 11:24:49 am »
Why not use TThreadList as queue? Either from classes or - in trunk - possibly the rtl-generics variant.
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus