C#循环中的多重ping操作

4

我需要创建一个应用程序,循环地ping多个地址。我在Stack Overflow上阅读了很多例子,并最终得到了可工作的代码:

    public void Check(List<string> addresses)
    {
        List<Task<PingReply>> pingTasks = new List<Task<PingReply>>();
        foreach (string address in addresses)
        {
            pingTasks.Add(PingAsync(address));
        }

        Task.Factory.ContinueWhenAll(pingTasks.ToArray(), _ => { }).ContinueWith(t =>
        {
            StringBuilder pingResult = new StringBuilder();
            foreach (var pingTask in pingTasks)
            {
                pingResult.Append(pingTask.Result.Address);
                pingResult.Append("    ");
                pingResult.Append(pingTask.Result.Status);
                pingResult.Append("    ");
                pingResult.Append(pingTask.Result.RoundtripTime.ToString());
                pingResult.Append("   \n");
            }
            Console.WriteLine(pingResult.ToString());
        },
        CancellationToken.None,
        TaskContinuationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
    }

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

现在我需要将这段代码改为使用await和async,并且在循环中以间隔执行。这里我的问题开始了。我不知道在这种情况下如何使用async,我阅读了许多文章,但是我的代码仍然无法运行。您能逐步解释给我如何更改代码以与await配合使用吗?您能告诉我如何将其放入while循环中并执行间隔吗?我尝试将整个“Check”函数放入循环中,并在末尾添加Thread.Sleep(interval),但我有一种奇怪的感觉我正在做错误/低效的事情。我需要在1秒钟内ping 400个服务器。这是否可能?
此致敬礼
更新1: 到目前为止,我的代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Linq;

namespace Pinging
{
    class CheckPing
    {
        public async Task LoopAndCheckPingAsync(List<string> addresses)
        {
            while (true)
            {
                var ping = new Ping();
                var pingTasks = addresses.Select(address => ping.SendPingAsync(address));

                await Task.WhenAll(pingTasks);

                StringBuilder pingResultBuilder = new StringBuilder();

                foreach (var pingReply in pingTasks)
                {
                    pingResultBuilder.Append(pingReply.Result.Address);
                    pingResultBuilder.Append("    ");
                    pingResultBuilder.Append(pingReply.Result.Status);
                    pingResultBuilder.Append("    ");
                    pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
                    pingResultBuilder.AppendLine();
                }

                Console.WriteLine(pingResultBuilder.ToString());

                await Task.Delay(TimeSpan.FromMinutes(5));
            }
        }
    }
}

以及调用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pinging
{
    public class Class1
    {
        static void Main()
        {
            List<string> addresses = new List<string>() { "www.google.pl", "212.77.100.101" };

            CheckPing c = new CheckPing();
            Task.Factory.StartNew(() => c.LoopAndCheckPingAsync(addresses));

            Console.Read();
        }
    }
}

我尝试以不同的方式从主函数调用 LoopAndCheckPingAsync,但仍然出现了冻结。这是我的最后一次尝试。

编辑2: 我对应用程序性能进行了一些更改,现在我的代码看起来像这样:

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Linq;

namespace Pinging
{
    class CheckPing
    {
        public async Task LoopAndCheckPingAsync(List<string> addresses)
        {
            while (true)
            {
                var pingTasks = addresses.Select(address =>
                {
                    return new Ping().SendPingAsync(address);
                });

                await Task.WhenAll(pingTasks);

                StringBuilder pingResultBuilder = new StringBuilder();

                foreach (var pingReply in pingTasks)
                {
                    pingResultBuilder.Append(pingReply.Result.Address);
                    pingResultBuilder.Append("    ");

                    pingResultBuilder.Append(pingReply.Result.Status);
                    pingResultBuilder.Append("    ");

                    pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
                    pingResultBuilder.AppendLine();
                }

                Console.WriteLine(pingResultBuilder.ToString());
                Functions.counter++;

                if (Functions.counter >= 100) break;

                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pinging
{
    public class Class1
    {
        static void Main()
        {
            List<string> addresses = Functions.Read(@"C:\Users\Adam\Desktop\addresses.csv");

            Functions.start = DateTime.Now;

            CheckPing c = new CheckPing();
            c.LoopAndCheckPingAsync(addresses).Wait();

            Console.WriteLine(Functions.counter);

            Console.Read();
        }
    }
}

我正在使用从文件中读取的标准网站地址:
www.google.com
www.yahoo.com
www.live.com
www.msn.com
www.facebook.com
www.youtube.com
www.microsoft.com
www.wikipedia.org
www.myspace.com
www.ebay.com
www.aol.com
www.ask.com
www.craigslist.org
www.blogspot.com
www.answers.com
www.about.com
www.amazon.com
www.mapquest.com
www.windows.com
www.adobe.com
www.photobucket.com
www.wordpress.com
www.go.com
www.paypal.com
www.walmart.com
www.reference.com
www.cnn.com
www.twitter.com
www.imdb.com
www.flickr.com
www.att.com
www.cnet.com
www.irs.gov
www.whitepages.com
www.yellowpages.com
www.comcast.net
www.target.com
www.simplyhired.com
www.webmd.com
www.weather.com
www.blogger.com
www.bankofamerica.com
www.apple.com
www.chase.com
www.bizrate.com
www.hulu.com
www.merriam-webster.com
www.geocities.com
www.ehow.com
www.ezinearticles.com
编辑3: 现在一切运行良好,但这里有另一个问题需要处理。当我在5分钟内测试100000个ping时,会出现内存不足异常。是否有办法在处理此问题?也许分成块并销毁旧类? 编辑4: 错误内容:

System.OutOfMemoryException was unhandled HResult=-2147024882 Message=Exception of type 'System.OutOfMemoryException' was thrown. Source=mscorlib StackTrace: at System.Exception.Init() at System.InvalidOperationException..ctor(String message, Exception innerException) at System.Net.NetworkInformation.PingException..ctor(String message, Exception innerException) at System.Net.NetworkInformation.Ping.ContinueAsyncSend(Object state) at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
InnerException:

编辑5:添加using语句后,我收到“没有足够的存储空间来处理此命令”的错误:
未处理的System.AggregateException异常 结果=-2146233088, 消息=发生一个或多个错误。 来源=mscorlib 堆栈跟踪: 在System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) 在System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 在System.Threading.Tasks.Task.Wait() 在Pinging.Class1.Main() 在System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) 在System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) 在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 在System.Threading.ThreadHelper.ThreadStart_Context(Object state) 在System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 在System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 在System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 在System.Threading.ThreadHelper.ThreadStart() 内部异常: System.Net.NetworkInformation.PingException 结果=-2146233079 消息=Ping请求期间发生异常。 来源=mscorlib 堆栈跟踪: 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() 在Pinging.CheckPing.d__2.MoveNext() 内部异常: System.ApplicationException 结果=-2147024888 消息=没有足够的存储空间来处理此命令(来自HRESULT的异常:0x80070008) 来源=mscorlib 堆栈跟踪: 在System.Threading.ThreadPool.RegisterWaitForSingleObjectNative(WaitHandle waitHandle, Object state, UInt32 timeOutInterval, Boolean executeOnlyOnce, RegisteredWaitHandle registeredWaitHandle, StackCrawlMark& stackMark, Boolean compressStack) 在System.Threading.ThreadPool.RegisterWaitForSingleObject(WaitHandle waitObject, WaitOrTimerCallback callBack, Object state, UInt32 millisecondsTimeOutInterval, Boolean executeOnlyOnce, StackCrawlMark& stackMark, Boolean compressStack) 在System.Threading.ThreadPool.RegisterWaitForSingleObject(WaitHandle waitObject, WaitOrTimerCallback callBack, Object state, Int32 millisecondsTimeOutInterval, Boolean executeOnlyOnce) 在System.Net.NetworkInformation.Ping.InternalSend(IPAddress address, Byte[] buffer, Int32 timeout, PingOptions options, Boolean async) 在System.Net.NetworkInformation.Ping.ContinueAsyncSend(Object state) 内部异常:
2个回答

6
让我们来看看我们想要做的事情:
  1. 当我们第一次执行该方法时,我们想要开始while循环。

  2. 我们想要生成一堆任务来发送ping请求。为此,我们可以使用Ping.SendPingAsync。我们将使用Enumerable.Select从地址列表中投影每个元素。

  3. 我们将等待所有任务完成执行。为此,我们将在Task.WhenAll上使用await

  4. 当所有任务完成执行ping请求后,我们将使用foreach循环对它们进行迭代。

  5. 我们将等待调用之间的时间间隔。我们不会使用Thread.Sleep,因为它是一个阻塞调用。相反,我们将使用Task.Delay,内部将使用一个Timer。当我们等待它时,控制权将返回调用我们的方法。

这就是它的样子:

private static async Task LoopAndCheckPingAsync(List<string> addresses)
{  
    StringBuilder pingResultBuilder = new StringBuilder();        

    while (true)
    {
         var pingTasks = addresses.Select(address =>
         {
             using (var ping = new Ping())
             {
                 return ping.SendPingAsync(address);
             }
         }).ToList();    

        await Task.WhenAll(pingTasks);

        foreach (var pingReply in pingTasks)
        {                pingResultBuilder.Append(pingReply.Result.Address);
            pingResultBuilder.Append("    ");
            pingResultBuilder.Append(pingReply.Result.Status);
            pingResultBuilder.Append("    ");

            pingResultBuilder.Append(pingReply.Result.RoundtripTime.ToString());
            pingResultBuilder.AppendLine();
        }

        Console.WriteLine(pingResultBuilder.ToString());
        pingResultBuilder.Clear();

        await Task.Delay(TimeSpan.FromMinutes(5));
    }
}

注意,该方法现在返回一个 Task 而不是 void,因为我们需要在方法上使用 await (请注意,一旦开始使用它,async 倾向于在代码库中扩散)。 编辑 经过对 Ping 的深入研究,显然我们不能在同一个 Ping 实例上执行多个 ping 请求(查看 Ping.CheckStart,它检查是否有正在进行的请求,如果有则抛出 InvalidOperationException),这正是我们在 Select 方法中所做的。为了解决这个问题,我们可以为每个请求创建一个 Ping 类的实例。请注意,这将给您的应用程序增加一些内存压力。如果您有 1000 个并发请求,那么在进行这些请求时,您将在内存中拥有 1000 个 Ping 类的实例。
另一个需要注意的事项是,您正在运行 Console 应用程序,它在内部使用 ThreadPoolSynchronizationContext。没有必要调用 Task.Run 来执行我们的方法,您可以使用 Task.Wait 在进行请求时保持控制台应用程序处于活动状态。最好使用 Wait,这样我们就可以看到是否有任何异常从我们的方法传播过来。
static void Main()
{
    List<string> addresses = new List<string>() { "www.google.pl", "212.77.100.101" };

    CheckPing c = new CheckPing();
    c.LoopAndCheckPingAsync(addresses).Wait();
}

你好,我又有一个关于我的应用程序的问题,希望你能帮助我。一切都运行得很完美,但如果任何ping不可达,我会收到这个回复:0.0.0.0超时。有没有办法以快速的方式检索发送的HostName而不是地址?我在这里找到了一些解决方案,但它们都使用ping.SendAsync而不是ping.SendPingAsync。 - Adam Mrozek
@AdamMrozek 请将其作为一个新问题发布,我会看一下的 :) - Yuval Itzchakov
这是我的问题。谢谢 :) http://stackoverflow.com/questions/25534085/retrieving-ping-host-name-from-sendpingasync-result - Adam Mrozek

1
我建议您考虑使用微软的响应式框架(NuGet“Rx-Main”)来解决这个问题。以下是代码示例:
private static void LoopAndCheckPingAsync(List<string> addresses)
{
    Func<string, IObservable<PingReply>> getPingReply = a =>
        Observable.Using(
            () => new Ping(),
            p => Observable.FromAsync<PingReply>(() => p.SendPingAsync(a)));

    var query =
        from n in Observable.Interval(TimeSpan.FromSeconds(5)).StartWith(-1L)
        from ps in
        (
            from a in addresses.ToObservable()
            from pr in getPingReply(a)
            select pr
        ).ToArray()
        select String.Join(
            Environment.NewLine,
            ps.Select(p => String.Format("{0}    {1}    {2}",
                    p.Address,
                    p.Status,
                    p.RoundtripTime)));

    query.Subscribe(x => Console.WriteLine(x));
}

就我所看到的,这是完全异步的,并且遵循您的要求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接