Jump to content
Shrinavat

Parallel processing in delphi: handling ping results from multiple threads

Recommended Posts

I'm using the Async abstraction from the OmniThreadLibrary to ping multiple hosts in background threads (one thread per host). In the main thread, I want to perform some actions with the ping results based on the host ID. Here's a simplified version of my code:

procedure Test;
var
  arr_id: TArray<LongWord>;
  arr_ip: TArray<string>;
begin
  arr_id := [1,2,3,4,5,6,7];
  arr_ip := ['192.168.16.1','192.168.16.2','192.168.16.3','192.168.16.4','192.168.16.5','192.168.16.6','192.168.16.7'];

  for var i := Low(arr_id) to High(arr_id) do
  begin
    Parallel.Async(
      procedure (const task: IOmniTask)
      var
        PingResult: Boolean;
        rec_id: LongWord;
        rec_ip: string;
      begin // background thread
        rec_id := arr_id[i];
        rec_ip := arr_ip[i];
        // PingResult := PingHost(rec_ip);

        // in the main thread
        task.Invoke(
          procedure
          begin
            // some actions with ping results based on host ID
            mmoLog.Lines.Add(Format('rec_id=%d, rec_ip=%s', [rec_id, rec_ip]));
          end);
      end);
  end;
end;

I'm getting inconsistent results. For example:

rec_id=3, rec_ip=192.168.16.3
rec_id=3, rec_ip=192.168.16.3
rec_id=135299280, rec_ip=烬Ŧ
rec_id=135299280, rec_ip=烬Ŧ
rec_id=135299280, rec_ip=烬Ŧ
rec_id=135299280, rec_ip=烬Ŧ
rec_id=135299280, rec_ip=烬Ŧ
 or

rec_id=2, rec_ip=192.168.16.2
rec_id=3, rec_ip=192.168.16.3
rec_id=4, rec_ip=192.168.16.4
rec_id=7, rec_ip=192.168.16.7
rec_id=7, rec_ip=192.168.16.7
rec_id=7, rec_ip=192.168.16.7
rec_id=135299280, rec_ip=烬Ŧ

 

Not all IPs are being pinged, and some are pinged multiple times. Also, I get garbage data in some cases.

How can I achieve the desired behavior, where each IP is pinged once and the results are processed correctly in the main thread? Alternatively, how can I rewrite the code using TTask?

Share this post


Link to post

Hello,

I think the problem is in the use of the variable "i" inside the async method. You have to make the variable "i" enter the procedure as a parameter of the procedure itself!

Something like:

 

for var i := Low(arr_id) to High(arr_id) do
  begin
    Parallel.Async(
      procedure (const task: IOmniTask)
    begin
    DOPING(task,i);        
    end);
end;

 procedure DOPING(const task: IOmniTask;i:integer)
      var
        PingResult: Boolean;
        rec_id: LongWord;
        rec_ip: string;
      begin // background thread
        rec_id := arr_id[i];
        rec_ip := arr_ip[i];
        // PingResult := PingHost(rec_ip);

        // in the main thread
        task.Invoke(
          procedure
          begin
            // some actions with ping results based on host ID
            mmoLog.Lines.Add(Format('rec_id=%d, rec_ip=%s', [rec_id, rec_ip]));
          end);
      end;

This I think should solve it (I can't check now), however it can also be done in many other ways.

 

 

 

 

  • Thanks 1

Share this post


Link to post

The issue stemmed from how variables were captured within the closure passed to Parallel.Async. The loop variable i was not correctly captured, leading to incorrect values being used in the background tasks.
The following code demonstrates my working solution:

procedure Test;
var
  arr_id: TArray<LongWord>;
  arr_ip: TArray<string>;

  procedure CreatePingTask(const id: LongWord; const ip: string);
  begin
    Parallel.Async(
      procedure (const task: IOmniTask)
      var
        PingResult: Boolean;
      begin // background thread
        // Simulate ping
        PingResult := Random(2) = 1; // random result for demonstration
        Sleep(Random(5000)); // simulate ping delay

        // in the main thread
        task.Invoke(
          procedure
          begin
            mmoLog.Lines.Add(Format('PingResult=%s, rec_id=%d, rec_ip=%s', [PingResult.ToString, id, ip]));
          end);
      end);
  end;

begin
  arr_id := [1,2,3,4,5,6,7];
  arr_ip := ['192.168.16.1','192.168.16.2','192.168.16.3','192.168.16.4','192.168.16.5','192.168.16.6','192.168.16.7'];

  for var i := Low(arr_id) to High(arr_id) do
    CreatePingTask(arr_id[i], arr_ip[i]);
end;

Explanation:
To ensure each background task receives the correct ID and IP, we pass them as parameters to the CreatePingTask procedure. This creates a separate copy of the variables for each task.
task.Invoke is used to synchronize with the main thread and update the UI with the ping results.
This approach ensures that each host is pinged exactly once and the results are processed correctly in the main thread.

I'd love to get some expert opinions on my solution. Are there any "gotchas" I should be watching out for?

Share this post


Link to post

You can make a hash of the IP and include that in the payload of the IcmpSendEcho2 call.
Then compare those with the return payload and if they match you are done.

 

Else store that info in an array or pass it to a callback to check in another/main thread.

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×