[英]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
狀態開始。 然而,這里真正重要的是它們永遠不會完成。
自原始帖子以來,我嘗試了以下操作:
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.