Recent

Author Topic: Updating charts from threads  (Read 1056 times)

kapibara

  • Hero Member
  • *****
  • Posts: 649
Updating charts from threads
« on: February 27, 2025, 03:43:34 am »
A series of a chart is updated from threads by a callback procedure in the main form. The callback adds a value to an associated ListChartsource and the chart should reflect that. Is the chart updated in a threadsafe way using synchronize like below?

Code: Pascal  [Select][+][-]
  1. uses
  2.   uMyform:
  3.  
  4. procedure TMyThread.Execute;
  5. begin
  6.   Synchronize(Myform.AddPointToChart);
  7. end;
  8.  
  9. procedure TMyform.AddPointToChart;
  10. begin
  11.   ListchartSource.Add(ATimeStamp, AValue);
  12. end;
« Last Edit: February 27, 2025, 06:23:46 am by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 12910
Re: Updating charts from threads
« Reply #1 on: March 02, 2025, 01:38:17 am »
Can you try and report the results back?

kapibara

  • Hero Member
  • *****
  • Posts: 649
Re: Updating charts from threads
« Reply #2 on: March 02, 2025, 02:15:24 am »
This is not implemented in the program yet. But soon. I'll report back then. AI seems to be OK with it:

My question:

ListChartSource.Add is protected by synchronize, but i wonder if the rest of the chain of updating the associated series and chart itself really is handled in a threadsafe way. Could you explain exactly how in that case?'

AI: In Lazarus, when using TAChart, understanding thread safety during chart updates involves considering not just the direct call to ListChartSource.Add, but also the ensuing changes that propagate to the chart series and the rendering of the chart itself. Here’s a breakdown of how you can assess the thread safety of the entire chain of updating the chart.
Fundamental Concepts

    UI Thread vs. Worker Threads: UI components (like charts) in Lazarus must be updated on the main (UI) thread. This is where the Synchronize method comes into play, as it helps transition execution from a worker thread back to the main thread in a controlled manner.

    Linking Components: In TAChart, adding data points to the data source (like ListChartSource) generally triggers updates to associated series and the chart display, but it's essential to understand how these updates are handled internally.

Mechanism of Synchronization

When you use Synchronize, it effectively queues your AddPointToChart method to be executed on the main thread, ensuring that the following chain of events is safely handled:

    Data Addition:
        The call to ListChartSource.Add(ATimeStamp, AValue) is performed on the main thread.
        By doing this, you ensure that any associated series that depend on this data source are correctly notified of the change.

    Notification of Updates:
        Within ListChartSource.Add, any internal mechanisms (e.g., event triggers) that notify associated series of new data points will also happen in the context of the main thread.
        This is crucial because TAChart may rely on certain events to update the series and refresh the chart.

    Chart Redrawing:
        If the data series is linked to the chart (e.g., using a TLineSeries or other series types), upon updating the data source, the series will typically trigger a redraw of the chart.
        This redraw process, being initiated from the main thread, ensures that all rendering logic operates correctly without risking corruption from concurrent access.

Additional Considerations

While Synchronize does help in controlling the access to ListChartSource, it is still important to pay attention to the following:

    Batch Updates: If your application expects to make many updates quickly, consider batching your updates. You might accumulate several points in the thread and call Synchronize only once after several additions, thus minimizing the context switches.

    Concurrency Implications: If you have other shapes or components that interact with the chart data or UI, they should also be either isolated in their own synchronized methods or safely access shared resources.

    Event Handling: If you are using event handlers to react to changes in the data source (like OnChange events), make sure these handlers also operate within the context of the main thread.

Summary

In summary, the approach you've described with Synchronize for updating the ListChartSource provides a suitable method for ensuring thread safety when working with TAChart in Lazarus. As long as you encapsulate all interactions with UI components (data additions, notifications, and rendering updates) within synchronized calls or are careful with how they are accessed, the chain of events involved in updating the chart should be handled in a thread-safe manner. Always remember to test under various conditions to ensure that your implementation behaves as expected.
« Last Edit: March 02, 2025, 02:19:32 am by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

wp

  • Hero Member
  • *****
  • Posts: 12910
Re: Updating charts from threads
« Reply #3 on: March 02, 2025, 12:01:35 pm »
I forgot that I do have an application in which a chart receives data values from a digital voltmeter read out in a thread (https://github.com/wp-xyz/datalogger). Normally I leave the program alone when acquiring data, but now I really stressed the application during the measurement by permanently resizing the window which always causes a redraw of the chart, and I did not notice any issues.

The thread's execute method has a simple Synchronize call to send the aquired data to the chart:
Code: Pascal  [Select][+][-]
  1. procedure TVC820Thread.Execute;
  2. begin
  3.   BufferSize := 14;
  4.  
  5.   FSerial.RTS := false;
  6.   FSerial.DTR := true;
  7.  
  8.   while not Terminated do begin
  9.     // in case that the data are out of sync: look for the first data byte
  10.     repeat
  11.       FBuffer[0] := FSerial.RecvByte(TimeOut);
  12.       FStatus := FSerial.LastError;
  13.     until (GetByteNumber(FBuffer[0]) = 1) or (FStatus <> 0);
  14.     if FStatus = 0 then begin
  15.       FSerial.RecvBufferEx(@FBuffer[1], BufferSize-1, TimeOut);
  16.       FStatus := FSerial.LastError;
  17.       Synchronize(@DoDataAvail)
  18.     end else
  19.       Synchronize(@DoError);
  20.   end;
  21. end;
DoDataAvail simply fires an OnDataAvail event. Its handler in the main form adds the new data to a list and redraws the chart based on this list (using a TUserDefinedChartSource).

Not sure whether this answers your question in the most general way because the data list, of course, is only written in this single way.
« Last Edit: March 02, 2025, 12:03:30 pm by wp »

kapibara

  • Hero Member
  • *****
  • Posts: 649
Re: Updating charts from threads
« Reply #4 on: March 02, 2025, 04:50:40 pm »
I think so, since there is only one single workthread modifying the listsource.

The reading and possibly writing done by TAChart itself will hopefully be serialized together with the workthreads call thanks to Synchronize. A TCriticalSection could also be used, but if its not needed its better to avoid the overhead. We'll see.
« Last Edit: March 02, 2025, 04:57:15 pm by kapibara »
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

kapibara

  • Hero Member
  • *****
  • Posts: 649
Re: Updating charts from threads
« Reply #5 on: March 31, 2025, 02:51:10 pm »
Success! The program has been running for some time now and no hickups at all with updates from the background thread updating TAChart.
Lazarus trunk / fpc 3.2.2 / Kubuntu 24.04 - 64 bit

 

TinyPortal © 2005-2018