-1

I am using following code to ping to multiple IP's at the same time (one ping repeated 4 times). In general it works fine. But the only problem I have is, when I want to get the ping replies to show them for example on the screen.

Let's say ip1 is online and the ping reply is OK. ip2 is offline and the ping reply is "timed Out" When I want to show the ping results on the screen, while pinging I want that for ip1 the ping result is there immediatly when the reply comes. But the "OK" results for ip1 is coming delayed with the "timed out" replies of ip2.

How can I isolate the ping results so that in my example ip1's result is shown immediatly without waiting each time 4 seconds for the "timed out" message?

private async ValueTask PingMachine() 
{
  var timesToPing = 4;
  var counter = 1;

  while (counter <= timesToPing) {
    var reply = await Pinger();
    foreach (var r in reply) {
       TagService.log_PLC.AppendLine($" Pinged {TagService.IP_PLC_List} {counter} times time:{r.RoundtripTime} status: {r.Status.ToString()}");
    }
}
counter++;
}

private async Task<IEnumerable<PingReply>> Pinger() 
{
  List<string> addresses = new List<string>();
  if (Check_IP_Correct(TagService.IP_PLC_List) == true) 
  {
   addresses.Add(TagService.IP_PLC_List);
  }

  var tasks = addresses.Select(async ip => await new Ping().SendPingAsync(ip, 1000));
  var reply = await Task.WhenAll(tasks);
  return reply;
}
Mdarende
  • 469
  • 3
  • 13
  • I think you should be able to chain a `ContinueWith` call onto the `SendPingAsync` call and perform an additional action for that specific task. In your case, that action would be displaying the result of that ping. It's not something I've done so I have no example at the ready but if you do a bit or research on `ContinueWith` then you ought to be able to find enough to go on. – jmcilhinney Aug 26 '23 at 08:53
  • See following code which uses a callback : https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=net-7.0 – jdweng Aug 26 '23 at 08:55
  • Related: [How to implement an efficient WhenEach that streams an IAsyncEnumerable of task results?](https://stackoverflow.com/questions/58194212/how-to-implement-an-efficient-wheneach-that-streams-an-iasyncenumerable-of-task) – Theodor Zoulias Aug 26 '23 at 08:57
  • Also related: [Task.WhenAll but process results one by one](https://stackoverflow.com/questions/62678367/task-whenall-but-process-results-one-by-one). – Theodor Zoulias Aug 26 '23 at 09:07

2 Answers2

1

You can pass a callback to the Pinger function so that the callback is executed as soon as the result is available.

To address Theodor Zouliak's concern about guideline CA2008, here's an edited version that doesn't use ContinueWith:

private async ValueTask PingMachine() 
{
  var timesToPing = 4;
  var counter = 1;

  while (counter <= timesToPing) {
    await Pinger(r => TagService.log_PLC.AppendLine($" Pinged {TagService.IP_PLC_List} {counter} times time:{r.RoundtripTime} status: {r.Status}"));
    counter++;
  }
}

private async Task<IEnumerable<PingReply>> Pinger(Action<PingReply> callback) 
{
  List<string> addresses = new List<string>();
  if (Check_IP_Correct(TagService.IP_PLC_List) == true) 
  {
    addresses.Add(TagService.IP_PLC_List);
  }

  var tasks = addresses.Select(ip => PingOne(ip, callback));
  var reply = await Task.WhenAll(tasks);
  return reply;
}

static private async Task<PingReply> PingOne(string ip, Action<PingReply> callback) {
  final r = await new Ping().SendPingAsync(ip, 1000);
  if (callback != null) {
    callback(r);
  }
  return r;
}

If you want everything to run concurrently, then do not await Pinger, eg:

  var tasks = new List<Task>();

  while (counter <= timesToPing) {
    tasks.Add(Pinger(r => TagService.log_PLC.AppendLine($" Pinged {TagService.IP_PLC_List} {counter} times time:{r.RoundtripTime} status: {r.Status}")));
    counter++;
  }

  await Task.WhenAll(tasks);
d-markey
  • 163
  • 1
  • 7
  • 1
    This usage of `ContinueWith` violates the guideline [CA2008](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2008) about not creating tasks without passing a `TaskScheduler` – Theodor Zoulias Aug 26 '23 at 09:08
  • @d-markey I have tried out the code. When all the devices are online and can reply to the ping it is working. But when one of the devices is offline, then the I get the first reply of the online device immedeatly, but the rest replies again each time together, I mean the online device reply is coming with offline device reply with delay (after 4 second timeout). – Mdarende Aug 26 '23 at 16:52
  • You mean if you have `n` devices with only 1 being offline, then you receive 1 online reply and `n-1` offline replies? The fact that responses for offline devices all come together is no surprise, for offline devices I suppose it is expected that offline responses all come at the same time after the same delay (= timeout delay). The strange thing is that you don't get 1 offline reply + `n-1` online replies. – d-markey Aug 26 '23 at 17:47
  • Now this also depends on the implementation of `Ping`, maybe it is not thread-safe? Your original version would only emit 1 ping request at a time and wait for the response. Now it sends `n` ping requests in parallel. Maybe `Ping` doesn't support concurrency. In which case you _believe_ it works when all are online, but maybe responses actually get mixed up. – d-markey Aug 26 '23 at 17:49
  • OK, I understand now: you have `n-1` online devices and 1 offline, and you get 1 online reply immediately, then all at the same time 1 offline reply + `n-2` online replies after timeout. So statuses are OK but the timing seems wrong. It's due to the implementation of `Ping` which actually queues ping requests. See code of [CheckStart](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.cs#L79) that is called from `SendPingAsyncInternal` which is called by `SendPingAsync`. – d-markey Aug 26 '23 at 17:57
  • On second thought, my previous comment is wrong. Your code creates different instances of `Ping` so there should be no concurrency problem. On what platform are you running your code? Have you tried a different one? – d-markey Aug 26 '23 at 18:03
  • I think @Stepen Cleary has explained what is missing "your current code is awaiting the response from every IP before displaying the results. To change it to display the results immediately, you just move the code that displays the results:" – Mdarende Aug 27 '23 at 09:26
  • I don't think so, Stephen's version should work the same. It simply does not abstract the callback. – d-markey Aug 27 '23 at 10:07
  • You are right, its working the same... Sorry but perhaps I couldn't explain it correctly. I write one more last time, without disturbing you so much: 1) IP1 is online , IP2 is offline 2) I am pinging to them at the same time with 4 ping repeats 3) I expect that the IP1 results (4 times OK) should come immediatly (As I would ping to just this one IP). The 4replies from IP2 (4 times timed out) should come in parallel with 4 second timeout waiting – Mdarende Aug 27 '23 at 10:28
  • 4) What is happening with your and Stephen's code: The first OK reply from IP1 comes immediatly, after 4 seconds the second OK reply from IP1 and the first TimedOut reply from IP2 comes, after 4 seconds the third OK reply from IP1 and the second TimedOut reply from IP2 comes...and so on.. – Mdarende Aug 27 '23 at 10:29
1

Almost every scenario where you find yourself thinking "I want to do something as each of these tasks completes", the answer is actually to adjust how your tasks are composed.

Your current code is awaiting the response from every IP before displaying the results. To change it to display the results immediately, you just move the code that displays the results:

private async ValueTask PingMachine() 
{
  var timesToPing = 4;
  var counter = 1;

  while (counter <= timesToPing) {
    var reply = await Pinger(counter);
    counter++;
  }
}

private async Task<IEnumerable<PingReply>> Pinger(int counter)
{
  List<string> addresses = new List<string>();
  if (Check_IP_Correct(TagService.IP_PLC_List) == true) 
    addresses.Add(TagService.IP_PLC_List);

  var tasks = addresses.Select(async ip =>
  {
    var result = await new Ping().SendPingAsync(ip, 1000));
    TagService.log_PLC.AppendLine($" Pinged {ip} {counter} times time:{result.RoundtripTime} status: {result.Status.ToString()}");
    return result;
  });
  return await Task.WhenAll(tasks);
}

Update:

From the comments, it seems like you want to have an independent loop with a separate counter for each IP address, rather than looping over all of them. In that case, you can move the loop into the lambda as well:

private async Task Pinger()
{
  var timesToPing = 4;
  List<string> addresses = new List<string>();
  if (Check_IP_Correct(TagService.IP_PLC_List) == true) 
    addresses.Add(TagService.IP_PLC_List);

  var tasks = addresses.Select(async ip =>
  {
    for (var counter = 1; counter <= timesToPing; ++counter)
    {
      var result = await new Ping().SendPingAsync(ip, 1000));
      TagService.log_PLC.AppendLine($" Pinged {ip} {counter} times time:{result.RoundtripTime} status: {result.Status.ToString()}");
    }
  });
  await Task.WhenAll(tasks);
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Hi Stephen, I think I am very near to the solution, but in your code, the last line "return await Task.WhenAll(tasks)" Is there any mistake, it can not be compiled? Compiler Error Error CS0029 Cannot implicitly convert type 'void' to 'System.Collections.Generic.IEnumerable' – Mdarende Aug 27 '23 at 08:42
  • 1
    That's due to the lambda not returning `result` – d-markey Aug 27 '23 at 10:05
  • You are right I have added return result to the Lambda but the result was the same like yours (@d-markey) – Mdarende Aug 27 '23 at 10:30
  • 1
    @Mdarende: See update. – Stephen Cleary Aug 28 '23 at 10:01
  • @StephenCleary Thank you so much Stephen. This was the cool solution I was looking for. Sorry that I have taken your time. Thank you again :-) – Mdarende Aug 28 '23 at 10:45
  • @d-markey Thank you also d-markey for supporting – Mdarende Aug 28 '23 at 10:51