简体   繁体   English

部分 Ping 任务从未完成

[英]Fraction of Ping Tasks never complete

I am trying to ping all link-local IP addresses on a particular interface ("169.254.xxx.yyy"; around 65000 of them).我正在尝试 ping 特定接口上的所有链接本地 IP 地址(“169.254.xxx.yyy”;大约 65000 个)。 I expect only one or two successes.我期望只有一两个成功。 I want to test the IP addresses that are active as early as possible (ie before waiting for all the other pings to time out);我想尽早测试处于活动状态的 IP 地址(即在等待所有其他 ping 超时之前); if an active address turns out to be the device I want then I can cancel all the other pings.如果活动地址是我想要的设备,那么我可以取消所有其他 ping。

My application is C#, WinForms, async/await.我的应用程序是 C#、WinForms、async/await。 I create a List<Task<T>> , each of which uses a separate Ping object to probe a particular address.我创建了一个List<Task<T>> ,每个都使用一个单独的 Ping 对象来探测特定地址。 I then use Task.WhenAny to retrieve the results and progressively remove the corresponding Task from the list.然后我使用Task.WhenAny来检索结果并从列表中逐步删除相应的任务。 (I've also tried the other, more efficient methods of handling the results in order, with similar results). (我还尝试了其他更有效的按顺序处理结果的方法,结果相似)。

What I have found is that, of the 65000 or so Tasks, most complete and deliver the appropriate result.我发现,在 65000 个左右的任务中,最完整并提供了适当的结果。 However, a few thousand of them (the precise number varies between runs) remain in the WaitingForActivation state and never get run .但是,其中有几千个(运行之间的精确数量不同)仍处于 WaitingForActivation 状态并且永远不会运行 Only if I reduce the number of Tasks to below about 1300 do I see correct completion of all of them;只有当我将任务数量减少到大约 1300 以下时,我才能看到所有任务都正确完成; otherwise, I see a fraction of them (around 5-10%) remain WaitingForActivation.否则,我会看到其中的一小部分(大约 5-10%)仍然是 WaitingForActivation。 The tasks that don't get run appear to be randomly distributed throughout the list.未运行的任务似乎随机分布在整个列表中。

I have tried moving the code to a Console application, with the same result.我尝试将代码移动到控制台应用程序,结果相同。 If in each Task I replace the use of SendPingAsync by a call to Task.Delay() with the same timeout, all Tasks complete as expected.如果在每个任务中我通过调用 Task.Delay() 替换使用 SendPingAsync 并具有相同的超时,则所有任务都按预期完成。

My Console test application:我的控制台测试应用程序:

using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Net.WebSockets;
using System.Threading.Tasks;

namespace AsyncPing
{
    class Program
    {
        static ClientWebSocket _commandWebSocket;

        static async Task Main(string[] args)
        {
            var topLevelTasks = new List<Task<ClientWebSocket>>();
            // Scanning task
            topLevelTasks.Add(Task.Run(async () =>
                await TryToConnectLinkLocal()));
            // Monitoring task (just for debugging)
            topLevelTasks.Add(Task.Run(async () => await MonitorLLTasks()));

            await Task.WhenAll(topLevelTasks);
        }

        // Monitoring Task; periodically reports on the state of the tasks collection
        private static async Task<ClientWebSocket> MonitorLLTasks()
        {
            for (int i = 0; i < 1000; ++i)
            {
                await Task.Delay(1000);

                int waitingForActivation = 0, waitingToRun = 0, running = 0, completed = 0;
                int index = 0;
                while (index < tasks.Count)
                {
                    try
                    {
                        switch (tasks[index].Status)
                        {
                            case TaskStatus.WaitingForActivation:
                                waitingForActivation++;
                                break;
                            case TaskStatus.WaitingToRun:
                                waitingToRun++;
                                break;
                            case TaskStatus.Running:
                                running++;
                                break;
                            case TaskStatus.RanToCompletion:
                                completed++;
                                break;
                        }

                        ++index;
                    }
                    catch
                    {
                        // Very occasionally, LLtasks[index] has been removed by the time we access it
                    }
                }
                Console.WriteLine($"There are {index} tasks: {waitingForActivation} waitingForActivation; {waitingToRun} waitingToRun; {running} running; {completed} completed.  {handled} results have been handled.");

                if (tasks.Count == 0)
                    break;
            }
            return null;
        }

        const string LinkLocalIPPrefix = "169.254.";

        static List<Task<String>> tasks = new List<Task<String>>();
        static int handled = 0;

        private static async Task<ClientWebSocket> TryToConnectLinkLocal()
        {
            // Link-local addresses all start with this prefix
            string baseIP = LinkLocalIPPrefix;

            tasks.Clear();
            handled = 0;

            Console.WriteLine("Scanning Link-local addresses...");
            // Scan all Link-local addresses
            // We build a task for each ip address.  
            // Note that there are nearly 65536 addresses to ping and the tasks start running
            // as soon as we start creating them. 
            for (int i = 1; i < 255; i++)
            {
                string ip_i = baseIP + i.ToString() + ".";
                for (int j = 1; j < 255; j++)
                {
                    string ip = ip_i + j.ToString();

                    var task = Task.Run(() => TryToConnectLinkLocal(ip));
                    tasks.Add(task);
                }
            }

            while (tasks.Count > 0)
            {
                var t = await Task.WhenAny(tasks);
                tasks.Remove(t);
                String result = await t;
                handled++;
            }

            return null;
        }
    
        private const int _pingTimeout = 10; // 10ms ought to be enough!

        // Ping the specified address
        static async Task<String> TryToConnectLinkLocal(string ip)
        {
            using (Ping ping = new Ping())
            {
                // This dummy code uses a fixed IP address to avoid possibility of a successful ping
                var reply = await ping.SendPingAsync("169.254.123.234", _pingTimeout);

                if (reply.Status == IPStatus.Success)
                {
                    Console.WriteLine("Response at LL address " + ip);
                    return ip;
                }
            }

            // Alternative: just wait for the duration of the timeout
            //await Task.Delay(_pingTimeout);

            return null;
        }

    }
}

A typical output would be something like (similar lines edited for brevity):典型的输出类似于(为简洁起见编辑了类似的行):

Scanning Link-local addresses...
There are 14802 tasks: 12942 waitingForActivation; 0 waitingToRun; 0 running; 1860 completed.  0 results have been handled.
There are 24623 tasks: 20005 waitingForActivation; 0 waitingToRun; 0 running; 4618 completed.  0 results have been handled.
There are 27287 tasks: 21170 waitingForActivation; 0 waitingToRun; 0 running; 6117 completed.  0 results have been handled.
There are 41714 tasks: 32471 waitingForActivation; 0 waitingToRun; 0 running; 9243 completed.  0 results have been handled.
There are 51263 tasks: 38816 waitingForActivation; 0 waitingToRun; 0 running; 12447 completed.  0 results have been handled.
There are 63891 tasks: 48403 waitingForActivation; 0 waitingToRun; 0 running; 15488 completed.  0 results have been handled.
There are 64498 tasks: 46496 waitingForActivation; 0 waitingToRun; 0 running; 18002 completed.  18 results have been handled.

<All tasks have been created. Many have been run.  More and more results are handled and the corresponding tasks removed>

There are 6626 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 1084 completed.  57890 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.
There are 5542 tasks: 5542 waitingForActivation; 0 waitingToRun; 0 running; 0 completed.  58974 results have been handled.

<5542 results remain in the list because they are stuck WaitingForActivation.  Only 58974 results (of 64516) have been handled.  This state continues indefinitely>

I would be happy to receive an explanation of this behaviour, suggestions for how to fix it, and/or suggestions about how to probe the network in a more efficient manner.我很乐意收到有关此行为的解释、如何修复它的建议和/或有关如何以更有效的方式探测网络的建议。

I've renamed this question because I understand from Stephen Cleary's blog that these tasks may be Promise tasks, which start in the WaitingForActivation state.我重命名了这个问题,因为我从 Stephen Cleary 的博客中了解到,这些任务可能是 Promise 任务,它们以WaitingForActivation状态开始。 What is really important here, however, is that they never complete.然而,这里真正重要的是它们永远不会完成。

Since the original post, I've tried the following:自原始帖子以来,我尝试了以下操作:

  • Used continuation tasks as per Stephen Toub's article to handle results in order of completion;按照 Stephen Toub 的文章使用延续任务按完成顺序处理结果;
  • Used ConcurrentExclusiveSchedulerPair to try to throttle the execution of the Tasks;使用ConcurrentExclusiveSchedulerPair尝试限制任务的执行;
  • Used ConfigureAwait(false) to try to vary the SynchronizationContext used;使用ConfigureAwait(false)尝试改变使用的SynchronizationContext
  • Checked that no exceptions are being thrown, as far as I can tell.据我所知,检查没有抛出异常。

None of these seemed to have any significant effect.这些似乎都没有任何显着影响。

I've also tried chunking the scan into a number of sub-scans.我还尝试将扫描分成多个子扫描。 The sub-scan generates a number of Ping tasks that are executed asynchronously (as per the code above).子扫描会生成一些异步执行的 Ping 任务(按照上面的代码)。 Each sub-scan is executed synchronously, one after the other.每个子扫描都是同步执行的,一个接一个。 On my machine, provided I keep the number of Ping tasks below about 1100, they all execute correctly.在我的机器上,如果我将 Ping 任务的数量保持在大约 1100 以下,它们都可以正确执行。 Above that, a fraction of them never complete.除此之外,其中一小部分永远不会完成。 It seems as if this approach is not all that much slower (presumably because the network interface gets flooded beyond a certain number of simultaneous pings), so it offers a practical approach to my problem.似乎这种方法并不是那么慢(大概是因为网络接口被淹没超过一定数量的同时 ping),所以它为我的问题提供了一种实用的方法。 However, the question of why some of the tasks fail to complete for >1100 tasks remains.但是,为什么有些任务无法完成超过 1100 个任务的问题仍然存在。 And still: if I replace the Ping by a call to await Task.Delay(...) , all tasks complete.仍然:如果我通过调用await Task.Delay(...)替换 Ping,则所有任务都完成。

My suggestion is to use an ActionBlock<T> from the TPL Dataflow library.我的建议是使用TPL 数据流库中的ActionBlock<T> This component will take care of the timely cancellation of the procedure when the first successful IPAddress is found, while also enforcing a maximum concurrency policy.当找到第一个成功的IPAddress时,该组件将负责及时取消该过程,同时还执行最大并发策略。

You start by instantiating an ActionBlock<IPAddress> , providing the action that will run for each IPAddress , as well as the execution options.您首先实例化ActionBlock<IPAddress> ,提供将为每个IPAddress运行的操作以及执行选项。 Then you feed the block with addresses, using the Post method.然后使用Post方法为块提供地址。 Then you signal that no more addresses will be posted by invoking the Complete method.然后您通过调用Complete方法发出信号,表示不会发布更多地址。 Finally you await the Completion property of the component.最后,您await组件的Completion属性。 Example:例子:

const int pingTimeout = 10;
using var cts = new CancellationTokenSource();
IPAddress result = null;

var block = new ActionBlock<IPAddress>(async address =>
{
    try
    {
        var reply = await new Ping().SendPingAsync(address, pingTimeout);
        if (reply.Status == IPStatus.Success)
        {
            Interlocked.CompareExchange(ref result, address, null);
            cts.Cancel();
        }
    }
    catch (PingException) { } // Ignore
}, new ExecutionDataflowBlockOptions()
{
    MaxDegreeOfParallelism = 100, // Select a reasonable value
    CancellationToken = cts.Token
});

byte b1 = 169, b2 = 254;
var addresses = Enumerable.Range(0, 255)
    .SelectMany(_ => Enumerable.Range(0, 255),
        (b3, b4) => new IPAddress(
            new byte[] { b1, b2, (byte)b3, (byte)b4 }));

foreach (var address in addresses) block.Post(address);
block.Complete();

try { await block.Completion; }
catch (OperationCanceledException) { } // Ignore

Console.WriteLine($"Result: {result?.ToString() ?? "(not found)"}");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM