C# Windows 异步 Pinging 网络 - 每个结果不同 运行

C# Windows Async Pinging Network - different results each run

我写了一个 class 异步 ping 一个子网。它有效,但是,返回的主机数量有时会在运行之间发生变化。一些问题:

ScanIPAddressesAsync()方法是这样调用的:

NetworkDiscovery nd = new NetworkDiscovery("192.168.50.");
nd.RaiseIPScanCompleteEvent += HandleScanComplete;
nd.ScanIPAddressesAsync();
namespace BPSTestTool
{

    public class IPScanCompleteEvent : EventArgs
    {
        public List<String> IPList { get; set; }

        public IPScanCompleteEvent(List<String> _list)
        {
            IPList = _list;
        }
    }

    public class NetworkDiscovery
    {
        private static object m_lockObj = new object();
        private List<String> m_ipsFound = new List<string>();
        private String m_ipBase = null;


        public List<String> IPList
        {
            get { return m_ipsFound; }
        }

        public EventHandler<IPScanCompleteEvent> RaiseIPScanCompleteEvent;

        public NetworkDiscovery(string ipBase)
        {
            this.m_ipBase = ipBase;
        }

        public async void ScanIPAddressesAsync()
        {
            var tasks = new List<Task>();

            m_ipsFound.Clear();
            await Task.Run(() => AsyncScan());
            return;
        }

        private async void AsyncScan()
        {
            List<Task> tasks = new List<Task>();
            for (int i = 2; i < 255; i++)
            {
                String ip = m_ipBase + i.ToString();

                if (m_ipsFound.Contains(ip) == false)
                {
                    for (int x = 0; x < 2; x++)
                    {
                        Ping p = new Ping();
                        var task = HandlePingReplyAsync(p, ip);
                        tasks.Add(task);
                    }
                }
            }
            await Task.WhenAll(tasks).ContinueWith(t =>
            {
                OnRaiseIPScanCompleteEvent(new IPScanCompleteEvent(m_ipsFound));
            });
        }

        protected virtual void OnRaiseIPScanCompleteEvent(IPScanCompleteEvent args)
        {
            RaiseIPScanCompleteEvent?.Invoke(this, args);
        }

        private async Task HandlePingReplyAsync(Ping ping, String ip)
        {
            PingReply reply = await ping.SendPingAsync(ip, 1500);

            if ( reply != null && reply.Status == System.Net.NetworkInformation.IPStatus.Success)
            {
                lock (m_lockObj)
                {
                    if (m_ipsFound.Contains(ip) == false)
                    {
                        m_ipsFound.Add(ip);
                    }
                }
            }
        }
    }
}

此代码需要进行大量重构,并且像这样的并发错误很难查明。我的赌注是 await Task.Run(() => AsyncScan());,这是错误的,因为 AsyncScan()async,而 Task.Run(...) 将 return 完成。

我的第二个猜测是 m_ipsFound,它被称为 共享状态 。这意味着可能有许多线程同时读取和写入它。 List<T> 不是此数据类型。

此外,在方法的最后一行有一个 return 的侧点不会增加可读性,并且 async void 是一种被禁止的做法。永远使用 async Task 即使你 return 什么都没有。您可以阅读更多关于 this 非常好的答案。

我看到的一个问题是 async void。甚至允许 async void 的唯一原因仅适用于事件处理程序。如果它不是事件处理程序,它就是一个危险信号。

异步方法始终同步启动 运行,直到第一个 await 作用于不完整的 Task。在您的代码中,即 await Task.WhenAll(tasks)。那时,AsyncScan returns - 在所有任务完成之前。通常,它会 return 一个 Task 让你知道它何时完成,但由于方法签名是 void,它不能。

所以现在看这个:

await Task.Run(() => AsyncScan());

AsyncScan() return 时,Task return 从 Task.Run 完成并且您的代码继续,之前所有 ping 都已完成

因此,当您报告结果时,结果的数量将是随机的,具体取决于在显示结果之前碰巧完成了多少。

如果您想确保在继续之前完成所有 ping,请将 AsyncScan() 更改为 return a Task:

private async Task AsyncScan()

并更改 Task.Run 等待它:

await Task.Run(async () => await AsyncScan());

但是,您也可以去掉 Task.Run,只使用这个:

await AsyncScan();

Task.Run 在单独的线程中运行代码。这样做的唯一原因是在 UI 应用程序中,您希望将 CPU 繁重的计算从 UI 线程中移出。当你只是做这样的网络请求时,那是没有必要的。

除此之外,您还在此处使用 async void

public async void ScanIPAddressesAsync()

这意味着无论你在哪里调用ScanIPAddressesAsync()都无法等到一切都完成。将其更改为 async Task 并等待它。