簡體   English   中英

部分 Ping 任務從未完成

[英]Fraction of Ping Tasks never complete

我正在嘗試 ping 特定接口上的所有鏈接本地 IP 地址(“169.254.xxx.yyy”;大約 65000 個)。 我期望只有一兩個成功。 我想盡早測試處於活動狀態的 IP 地址(即在等待所有其他 ping 超時之前); 如果活動地址是我想要的設備,那么我可以取消所有其他 ping。

我的應用程序是 C#、WinForms、async/await。 我創建了一個List<Task<T>> ,每個都使用一個單獨的 Ping 對象來探測特定地址。 然后我使用Task.WhenAny來檢索結果並從列表中逐步刪除相應的任務。 (我還嘗試了其他更有效的按順序處理結果的方法,結果相似)。

我發現,在 65000 個左右的任務中,最完整並提供了適當的結果。 但是,其中有幾千個(運行之間的精確數量不同)仍處於 WaitingForActivation 狀態並且永遠不會運行 只有當我將任務數量減少到大約 1300 以下時,我才能看到所有任務都正確完成; 否則,我會看到其中的一小部分(大約 5-10%)仍然是 WaitingForActivation。 未運行的任務似乎隨機分布在整個列表中。

我嘗試將代碼移動到控制台應用程序,結果相同。 如果在每個任務中我通過調用 Task.Delay() 替換使用 SendPingAsync 並具有相同的超時,則所有任務都按預期完成。

我的控制台測試應用程序:

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

    }
}

典型的輸出類似於(為簡潔起見編輯了類似的行):

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>

我很樂意收到有關此行為的解釋、如何修復它的建議和/或有關如何以更有效的方式探測網絡的建議。

我重命名了這個問題,因為我從 Stephen Cleary 的博客中了解到,這些任務可能是 Promise 任務,它們以WaitingForActivation狀態開始。 然而,這里真正重要的是它們永遠不會完成。

自原始帖子以來,我嘗試了以下操作:

  • 按照 Stephen Toub 的文章使用延續任務按完成順序處理結果;
  • 使用ConcurrentExclusiveSchedulerPair嘗試限制任務的執行;
  • 使用ConfigureAwait(false)嘗試改變使用的SynchronizationContext
  • 據我所知,檢查沒有拋出異常。

這些似乎都沒有任何顯着影響。

我還嘗試將掃描分成多個子掃描。 子掃描會生成一些異步執行的 Ping 任務(按照上面的代碼)。 每個子掃描都是同步執行的,一個接一個。 在我的機器上,如果我將 Ping 任務的數量保持在大約 1100 以下,它們都可以正確執行。 除此之外,其中一小部分永遠不會完成。 似乎這種方法並不是那么慢(大概是因為網絡接口被淹沒超過一定數量的同時 ping),所以它為我的問題提供了一種實用的方法。 但是,為什么有些任務無法完成超過 1100 個任務的問題仍然存在。 仍然:如果我通過調用await Task.Delay(...)替換 Ping,則所有任務都完成。

我的建議是使用TPL 數據流庫中的ActionBlock<T> 當找到第一個成功的IPAddress時,該組件將負責及時取消該過程,同時還執行最大並發策略。

您首先實例化ActionBlock<IPAddress> ,提供將為每個IPAddress運行的操作以及執行選項。 然后使用Post方法為塊提供地址。 然后您通過調用Complete方法發出信號,表示不會發布更多地址。 最后,您await組件的Completion屬性。 例子:

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