2

I am developing an application that pings several host. The list of hosts is read from a CSV file.
When there is a response, the program displays a green tick, a red cross when the ping fails.

This works great, but I need to display a third image (like a yellow explanation mark) when the Round Trip Time is more than 50ms.

This is my code at the moment:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    Thread.Sleep(500);
    Parallel.For(0, ipAddress.Count(), (i, loopState) =>
    {
        Ping ping = new Ping();
        PingReply pingReply = ping.Send(ipAddress[i].ToString());
        this.BeginInvoke((Action)delegate ()
        {
            pictureboxList[i].BackgroundImage = (pingReply.Status == IPStatus.Success) ? Image.FromFile(@"C:\Users\PC\Downloads\green.png") : Image.FromFile(@"C:\Users\PC\Downloads\red.png");
        });
    });
}

Is there a way to do it?

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • You can use the `RoundtripTime`, just add an `if` condition. -- You shouldn't invoke from the DoWork event, you should report progress; or use `Image.FromFile()`, you should probably use local objects taken from the Projetc's resources, setting properties of Controls in the `ProgressChanged` event. – Jimi Aug 18 '21 at 13:14
  • Hi Thanks, could you possibly provide an example? I have tried that the `if` doesn't seem to work –  Aug 18 '21 at 13:19
  • Maybe. For sure not using a BackgroundWorker, though. Probably a `List` + a `Progress` delegate to update the UI and calling `SendPingAsync()` , then awaiting `Task.WhenAll()`. -- If that's ok (and if someone doesn't come by with something like that in the meanwhile), I'll post it. – Jimi Aug 18 '21 at 13:33
  • Okay, thanks, i'll work on that and if I can get it working then I'll post the code –  Aug 18 '21 at 13:37

2 Answers2

2

An simple example using a List<Task> to generate a sequence of Ping requests, using a collection of IP Addresses (in the form of strings) provided by the caller and uses an IProgress<T> delegate to update the UI (Progress<T> captures the current SynchronizationContext, so code executed by the delegate, is executed in Thread that initialized it; the UI Thread, here).
For each address passed to the method, a PingAsync() Task is added to the list.

The PingAsync() method calls Ping.SendPingAsync() and reports back the results, either success or failure, as an object that can represent a PingReply, a PingException or a SocketException in the form of a SocketError (the Progress() method translates the SocketError to an IPStatus, to handle just one type of result. Add more cases if you need a more verbose / detailed response).

The Tasks generate a sequence (an int value) that is sent to the Progress<T> delegate, in case it needs it. Here, it's used to select a specific Control from the collection passed to the PingAll() method.

You can then handle these results in the Progress<T> delegate, to see what happened to the current Ping request and update your Controls.

Task.WhenAll() is then awaited. It will return when all Tasks complete. A Task completes when Ping succeeds or fails or when the specified timeout has elapsed.

The 3 images used to display the result status:

  • Green - IPStatus.Success and RoundtripTime <= 30
  • Yellow - IPStatus.Success and RoundtripTime > 30
  • Red - IPStatus != IPStatus.Success

are taken from the Project's resources. Better not get them from the File System here, you may introduce unnecessary complexity without any advantage.

Assume you initialize the MassPing class and await the results of PingAll() using the handler of a Button.Click (note that the handler is async):

private async void btnMassPing_Click(object sender, EventArgs e)
{
    btnMassPing.Enabled = false;
    // Create a collection of existing Controls that have a BackgroundImage property
    var controls = new Control[] { /* a list of existing Controls */ };
    // The Addresses count must match the Controls'
    var addresses = [An array of strings representing IpAddresses or Host names]
    var massPing = new MassPing();
    await massPing.PingAll(addresses, controls, 2000);
    btnMassPing.Enabled = true;
}

Note: for simplicity, the PingAll() method creates an IProgress<T> delegate all by itself. You may prefer to pass a delegate to this method instead, from the procedure that initializes the MassPing class.
This way, you don't need to pass a collection of Controls to the method.
It doesn't matter much if you use this class in a WinForms app, it does (or may) matter if you want to move the class to a Library.

using System.Collections.Generic;
using System.Drawing;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Windows.Forms;

public class MassPing
{
    private Bitmap imageRed = Properties.Resources.Red;
    private Bitmap imageGreen = Properties.Resources.Green;
    private Bitmap imageYellow = Properties.Resources.Yellow;

    public async Task PingAll(string[] addresses, Control[] controls, uint timeout = 2000)
    {
        // Add more checks on the arguments
        if (addresses.Length != controls.Length) {
            throw new ArgumentException("Collections length mismatch");
        }

        var obj = new object();
        var tasks = new List<Task>();

        var progress = new Progress<(int sequence, object reply)>(report => {
            lock (obj) {
                // Use the reply Status value to set any other Control. In this case, 
                // it's probably better to have a UserControl that shows multiple values
                var status = IPStatus.Unknown;
                if (report.reply is PingReply pr) {
                    status = pr.Status;
                    Bitmap img = status is IPStatus.Success
                        ? pr.RoundtripTime > 30 ? imageYellow : imageGreen
                        : imageRed;
                    controls[report.sequence].BackgroundImage?.Dispose();
                    controls[report.sequence].BackgroundImage = img;
                }
                else if (report.reply is SocketError socErr) {
                    if (socErr == SocketError.HostNotFound) {
                        status = IPStatus.DestinationHostUnreachable;
                    }
                    controls[report.sequence].BackgroundImage?.Dispose();
                    controls[report.sequence].BackgroundImage = imageRed;
                }
            }
        });

        // Add all tasks
        for (int seq = 0; seq < addresses.Length; seq++) {
            tasks.Add(PingAsync(addresses[seq], (int)timeout, seq, progress));
        }
        // Could use some exception handling 
        await Task.WhenAll(tasks);
    }

    private async Task PingAsync(string ipAddress, int timeOut, int sequence, IProgress<(int seq, object reply)> progress)
    {
        var buffer = new byte[32];
        var ping = new Ping();

        try {
            var options = new PingOptions(64, true);
            PingReply reply = await ping.SendPingAsync(ipAddress, timeOut, buffer, options);
            progress.Report((sequence, reply));
        }
        catch (PingException pex) {
            if (pex.InnerException is SocketException socEx) {
                progress.Report((sequence, socEx.SocketErrorCode));
            }
        }
        finally {
            ping.Dispose();
        }
    }
}
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Hi, Thanks I just understand what I need to do here at this line? `var addresses = [An array of strings representing IpAddresses or Host names]`? –  Aug 20 '21 at 10:52
0

To answer the initial question, I think it should be enough :

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(500);
        Parallel.For(0, ipAddress.Count(), (i, loopState) =>
        {
            Ping ping = new Ping();
            PingReply pingReply = ping.Send(ipAddress[i].ToString());
            this.BeginInvoke((Action)delegate ()
            {
                if (pingReply.Status == IPStatus.Success)
                {
                    if (pingReply.RoundtripTime > 50)
                        Image.FromFile(@"C:\Users\PC\Downloads\yellow.png");
                    else
                        Image.FromFile(@"C:\Users\PC\Downloads\green.png");
                }
                else
                {
                    Image.FromFile(@"C:\Users\PC\Downloads\red.png");
                }
            });
        });
    }

However, don't load images from disk again and again, keep it in variables to save disk access.

KiwiJaune
  • 530
  • 2
  • 16