Как выполнить несколько "пингов" в параллельном режиме с использованием С#

Я пытаюсь вычислить среднее время прохождения туда-обратно для коллекции серверов. Чтобы ускорить процесс, я хотел бы параллельно выполнять пинги. Я написал функцию под названием AverageRoundtripTime(), и, похоже, она работает, поскольку я не очень много знаю о многопоточности, мне интересно, все ли в порядке. Пожалуйста, взгляните на мой код и сообщите мне, хорошо ли это или если есть лучший способ добиться того, что я хочу:

public void Main()
{
    // Collection of hosts.
    List<String> hosts = new List<String>();

    // Add 100 hosts to the collection.
    for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");

    // Display the average round-trip time for 100 hosts.
    Console.WriteLine(AverageRoundtripTime(hosts));

}

public Double AverageRoundtripTime(IEnumerable<String> hosts)
{
    // Collection of threads.
    List<Thread> threads = new List<Thread>();

    // Collection of ping replies.
    List<PingReply> pingReplies = new List<PingReply>();

    // Loop through all host names.
    foreach (var host in hosts)
    {
        // Create a new thread.
        Thread thread = new Thread(() =>
        {
            // Variable to hold the ping reply.
            PingReply reply = null;

            // Create a new Ping object and make sure that it 
            // disposed after we're finished with it.
            using (Ping ping = new Ping())
            {
                    reply = ping.Send(host);

            }

            // Get exclusive lock on the pingReplies collection.
            lock (pingReplies)
            {
                // Add the ping reply to the collection.
                pingReplies.Add(reply);

            }

        });

        // Add the newly created thread to the theads collection.
        threads.Add(thread);

        // Start the thread.
        thread.Start();

    }

    // Wait for all threads to complete
    foreach (Thread thread in threads)
    {
        thread.Join();

    }

    // Calculate and return the average round-trip time.
    return pingReplies.Average(x => x.RoundtripTime);

}

Update:

Проверьте связанный с этим вопрос, который я спросил:

Задача Параллельный библиотечный код Замораживает в приложении Windows Forms - отлично работает как консольное приложение Windows

Ответ 1

Класс ping имеет метод SendAsync. Это следует за шаблоном асинхронного программирования на основе событий (EAP). Ознакомьтесь с этой статьей: http://msdn.microsoft.com/en-us/library/ee622454.aspx.

Для краткого примера здесь есть метод, который я использую для этой статьи очень простым способом. Вы можете называть это столько раз, сколько хотите, и все пинги будут выполняться асинхронно.

    class Program
    {
    public static string[] addresses = {"microsoft.com", "yahoo.com", "google.com"};
    static void Main(string[] args)
    {
        List<Task<PingReply>> pingTasks = new List<Task<PingReply>>();
        foreach (var address in addresses)
        {
            pingTasks.Add(PingAsync(address));
        }

        //Wait for all the tasks to complete
        Task.WaitAll(pingTasks.ToArray());

        //Now you can iterate over your list of pingTasks
        foreach (var pingTask in pingTasks)
        {
            //pingTask.Result is whatever type T was declared in PingAsync
            Console.WriteLine(pingTask.Result.RoundtripTime);
        }
        Console.ReadLine();
    }

    static Task<PingReply> PingAsync(string address)
    {
        var tcs = new TaskCompletionSource<PingReply>();
        Ping ping = new Ping();
        ping.PingCompleted += (obj, sender) =>
            {
                tcs.SetResult(sender.Reply);
            };
        ping.SendAsync(address, new object());
        return tcs.Task;
    }
}

Ответ 2

используйте Parallel.For и ConcurrentBag

    static void Main(string[] args)
    {
        Console.WriteLine(AverageRoundTripTime("www.google.com", 100));
        Console.WriteLine(AverageRoundTripTime("www.stackoverflow.com", 100));
        Console.ReadKey();
    }

    static double AverageRoundTripTime(string host, int sampleSize)
    {
        ConcurrentBag<double> values = new ConcurrentBag<double>();
        Parallel.For(1, sampleSize, (x, y) => values.Add(Ping(host)));
        return values.Sum(x => x) / sampleSize;
    }
    static double Ping(string host)
    {
        var reply = new Ping().Send(host);
        if (reply != null)
            return reply.RoundtripTime;
        throw new Exception("denied");
    }

Ответ 3

//Решение упрощается с помощью LINQ

List<String> hosts = new List<String>();
for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");

var average = hosts.AsParallel().WithDegreeOfParallelism(64).
              Select(h => new Ping().Send(h).RoundtripTime).Average();


Console.WriteLine(average)

Ответ 4

Возможно использование SendPingAsync следующим образом:

using (var ping = new Ping())
{
    var replies = await Task.WhenAll(hosts.Select(x => ping.SendPingAsync(x)))
                            .ConfigureAwait(false);
                            // false here   ^ unless you want to schedule back to sync context
    ... process replies.
}

Ответ 5

Решение:

internal class Utils
{
    internal static PingReply Ping (IPAddress address, int timeout = 1000, int ttl = 64)
    {
            PingReply tpr = null;
            var p = new Ping ();
            try {

                tpr = p.Send (address,
                    timeout,
                    Encoding.ASCII.GetBytes ("oooooooooooooooooooooooooooooooo"),
                    new PingOptions (ttl, true));

            } catch (Exception ex) {

                tpr = null;

            } finally {
                if (p != null)
                    p.Dispose ();

                p = null;
            }

            return tpr;
        }

        internal static List<PingReply> PingAddresses (List<IPAddress> addresses, int timeout = 1000, int ttl = 64)
        {
            var ret = addresses
                .Select (p => Ping (p, timeout, ttl))
                .Where (p => p != null)
                .Where (p => p.Status == IPStatus.Success)
                .Select (p => p).ToList ();

            return ret;
        }

        internal static Task PingAddressesAsync (List<IPAddress> addresses, Action<Task<List<PingReply>>> endOfPing, int timeout = 1000, int ttl = 64)
        {

            return Task.Factory.StartNew<List<PingReply>> (() => Utils.PingAddresses (
                addresses, timeout, ttl)).ContinueWith (t => endOfPing (t));

        }   
}

И используя:

Console.WriteLine ("start");

Utils.PingAddressesAsync (new List<IPAddress> () { 
                    IPAddress.Parse ("192.168.1.1"), 
                    IPAddress.Parse ("192.168.1.13"), 
                    IPAddress.Parse ("192.168.1.49"),
                    IPAddress.Parse ("192.168.1.200")
                }, delegate(Task<List<PingReply>> tpr) {

                    var lr = tpr.Result;
                    Console.WriteLine ("finish with " + lr.Count.ToString () + " machine found");

                    foreach (var pr in lr) {
                        Console.WriteLine (pr.Address.ToString ());
        }

});

Console.WriteLine ("execute");
Console.ReadLine ();

Ответ 6

Это асинхронный рабочий, который может пропинговать несколько конечных точек. Вы можете запустить() или остановить() пульса работника и подписаться на следующие события:

  • PingUp (запускается по краю, когда конечная точка отключается)
  • PingDown (запускается по краю, когда конечная точка поднимается)
  • PulseStarted
  • PulseEnded
  • PingError

-

public class NetworkHeartbeat
{
    private static object lockObj = new object();

    public bool Running { get; private set; }
    public int PingTimeout { get; private set; }
    public int HeartbeatDelay { get; private set; }
    public IPAddress[] EndPoints { get; private set; }
    public int Count => EndPoints.Length;
    public PingReply[] PingResults { get; private set; }
    private Ping[] Pings { get; set; }

    public NetworkHeartbeat(IEnumerable<IPAddress> hosts, int pingTimeout, int heartbeatDelay)
    {
        PingTimeout = pingTimeout;
        HeartbeatDelay = heartbeatDelay;

        EndPoints = hosts.ToArray();
        PingResults = new PingReply[EndPoints.Length];
        Pings = EndPoints.Select(h => new Ping()).ToArray();
    }

    public async void Start()
    {
        if (!Running)
        {
            try
            {
                Debug.WriteLine("Heartbeat : starting ...");

                // set up the tasks
                var chrono = new Stopwatch();
                var tasks = new Task<PingReply>[Count];

                Running = true;

                while (Running)
                {
                    // set up and run async ping tasks                 
                    OnPulseStarted(DateTime.Now, chrono.Elapsed);
                    chrono.Restart();
                    for (int i = 0; i < Count; i++)
                    {
                        tasks[i] = PingAndUpdateAsync(Pings[i], EndPoints[i], i);
                    }
                    await Task.WhenAll(tasks);

                    for (int i = 0; i < tasks.Length; i++)
                    {
                        var pingResult = tasks[i].Result;

                        if (pingResult != null)
                        {
                            if (PingResults[i] == null)
                            {
                                if (pingResult.Status == IPStatus.Success)
                                    OnPingUp(i);
                            }
                            else if (pingResult.Status != PingResults[i].Status)
                            {
                                if (pingResult.Status == IPStatus.Success)
                                    OnPingUp(i);
                                else if (PingResults[i].Status == IPStatus.Success)
                                    OnPingDown(i);
                            }
                        }
                        else
                        {
                            if (PingResults[i] != null && PingResults[i].Status == IPStatus.Success)
                                OnPingUp(i);
                        }

                        PingResults[i] = tasks[i].Result;
                        Debug.WriteLine("> Ping [" + PingResults[i].Status.ToString().ToUpper() + "] at " + EndPoints[i] + " in " + PingResults[i].RoundtripTime + " ms");
                    }

                    OnPulseEnded(DateTime.Now, chrono.Elapsed);

                    // heartbeat delay
                    var delay = Math.Max(0, HeartbeatDelay - (int)chrono.ElapsedMilliseconds);
                    await Task.Delay(delay);
                }
                Debug.Write("Heartbeat : stopped");
            }
            catch (Exception)
            {
                Debug.Write("Heartbeat : stopped after error");
                Running = false;
                throw;
            }
        }
        else
        {
            Debug.WriteLine("Heartbeat : already started ...");
        }
    }

    public void Stop()
    {
        Debug.WriteLine("Heartbeat : stopping ...");
        Running = false;
    }

    private async Task<PingReply> PingAndUpdateAsync(Ping ping, IPAddress epIP, int epIndex)
    {
        try
        {
            return await ping.SendPingAsync(epIP, PingTimeout);
        }
        catch (Exception ex)
        {
            Debug.Write("-[" + epIP + "] : error in SendPing()");
            OnPingError(epIndex, ex);
            return null;
        }
    }

    // Event on ping errors
    public event EventHandler<PingErrorEventArgs> PingError;
    public class PingErrorEventArgs : EventArgs
    {
        public int EndPointIndex { get; private set; }
        public Exception InnerException { get; private set; }

        public PingErrorEventArgs(int epIndex, Exception ex)
        {
            EndPointIndex = epIndex;
            InnerException = ex;
        }
    }
    private void OnPingError(int epIndex, Exception ex) => PingError?.Invoke(this, new PingErrorEventArgs(epIndex, ex));

    // Event on ping Down
    public event EventHandler<int> PingDown;
    private void OnPingDown(int epIndex)
    {
        Debug.WriteLine("# Ping [DOWN] at " + EndPoints[epIndex]);
        PingDown?.Invoke(this, epIndex);
    }

    // Event on ping Up
    public event EventHandler<int> PingUp;
    private void OnPingUp(int epIndex)
    {
        Debug.WriteLine("# Ping [UP] at " + EndPoints[epIndex] );
        PingUp?.Invoke(this, epIndex);
    }

    // Event on pulse started
    public event EventHandler<PulseEventArgs> PulseStarted;
    public class PulseEventArgs : EventArgs
    {
        public DateTime TimeStamp { get; private set; }
        public TimeSpan Delay { get; private set; }

        public PulseEventArgs(DateTime date, TimeSpan delay)
        {
            TimeStamp = date;
            Delay = delay;
        }
    }
    private void OnPulseStarted(DateTime date, TimeSpan delay)
    {
        Debug.WriteLine("# Heartbeat [PULSE START] after " + (int)delay.TotalMilliseconds + " ms");
        PulseStarted?.Invoke(this, new PulseEventArgs(date, delay));
    }

    // Event on pulse ended
    public event EventHandler<PulseEventArgs> PulseEnded;
    private void OnPulseEnded(DateTime date, TimeSpan delay)
    {
        PulseEnded?.Invoke(this, new PulseEventArgs(date, delay));
        Debug.WriteLine("# Heartbeat [PULSE END] after " + (int)delay.TotalMilliseconds + " ms");
    }
}