当 Ping 往返时间超过 50 毫秒时,如何更改 PictureBox 的图像?

How to change the image of a PictureBox when the Ping Round Trip Time is more that 50ms?

我正在开发一个可以 ping 多个主机的应用程序。主机列表是从 CSV 文件中读取的。
当有响应时,程序显示绿色勾号,ping 失败时显示红色叉号。

效果很好,但当往返时间超过 50ms 时,我需要显示第三张图片(如黄色解释标记)。

这是我目前的代码:

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");
        });
    });
}

有办法吗?

回答最初的问题,我觉得应该够了:

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");
                }
            });
        });
    }

但是,不要一次又一次地从磁盘加载图像,将其保存在变量中以节省磁盘访问。

一个使用 List<Task> 生成一系列 Ping 请求的简单示例,使用调用者提供的 IP 地址集合(以字符串形式)并使用 IProgress<T> 委托来更新 UI(Progress<T> 捕获当前的 SynchronizationContext,因此委托执行的代码在初始化它的线程中执行;这里是 UI 线程)。
对于传递给该方法的每个地址,都会将一个 PingAsync() 任务添加到列表中。

PingAsync() 方法调用 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,只处理一种类型的结果。如果您需要更详细/更详细的回复,请添加更多案例。

任务生成一个序列(一个 int 值)发送给 Progress<T> 代表,以备不时之需。在这里,它用于 select 集合中的特定控件传递给 PingAll() 方法。

然后您可以在 Progress<T> 委托中处理这些结果,以查看当前 Ping 请求发生了什么并更新您的控件。

然后等待

Task.WhenAll()。当所有任务完成时,它将 return。当 Ping 成功或失败或指定的超时已过时,任务完成。

用于显示结果状态的3张图片:

  • 绿色 - IPStatus.Success 和往返时间 <= 30
  • 黄色 - IPStatus.Success 且往返时间 > 30
  • 红色 - IPStatus != IPStatus.Success

取自项目的资源。最好不要在这里从文件系统中获取它们,您可能会引入不必要的复杂性而没有任何优势。

假设您使用 Button.Click 的处理程序初始化 MassPing class 并等待 PingAll() 的结果(注意处理程序是 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;
}

注意:为简单起见,PingAll() 方法自行创建了一个 IProgress<T> 委托。您可能更愿意从初始化 MassPing class.
的过程中将委托传递给此方法 这样,您就不需要将控件集合传递给该方法。
如果您在 WinForms 应用程序中使用此 class 并不重要,如果您想将 class 移动到库中,它确实(或可能)很重要。

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();
        }
    }
}