【问题标题】:C# Windows Async Pinging Network - different results each runC# Windows 异步 Ping 网络 - 每次运行的结果不同
【发布时间】:2019-12-30 18:27:48
【问题描述】:

我编写了一个异步 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);
                    }
                }
            }
        }
    }
}

【问题讨论】:

  • 附带说明,最好使用Async 作为后缀,而不是AsyncScan,并将此方法命名为ScanAsync,以符合guidlines
  • @TheodorZoulias 感谢您的提示和链接。我很感激。
  • 对我的问题投赞成票怎么样? :-) 似乎它可能对其他犯过我同样错误的人有用。

标签: c# asynchronous async-await ping


【解决方案1】:

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

异步方法总是开始同步运行,直到第一个await 作用于不完整的Task。在您的代码中,位于await Task.WhenAll(tasks)。此时,AsyncScan 返回 - 在所有任务完成之前。通常,它会返回一个Task,让你知道它什么时候完成,但由于方法签名是void,它不能。

现在看看这个:

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

AsyncScan() 返回时,从Task.Run 返回的Task 完成并且您的代码继续前进,在所有 ping 完成之前

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

如果您想确保在继续之前完成所有 ping 操作,请更改 AsyncScan() 以返回 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 并等待它。

【讨论】:

  • 谢谢。我现在正在等待 Task.Run。是否有可能某些设备无法始终及时回复?我会看到 16 位主机连续回复 10 次,然后我很少回复 15 位主机。
  • Awaiting Task.Run 对您没有帮助,因为它返回的 Task 在所有 ping 完成之前就完成了。原因是您使用了async void。您需要更改 AsyncScan 以返回 Taskawait AsyncScan()
  • 我也刚刚注意到async void ScanIPAddressesAsync()。你也需要改变它。我更新了我的答案。
  • 再次感谢。是的,我将AsyncScan 更改为返回Task
  • 非常感谢您的帮助。我已经进行了建议的更改。只是为了比较,我正在运行 Angry IP Scanner,它有时也会返回 15 个主机。所以我认为我的代码现在更正确了,感谢您的建议。
【解决方案2】:

这段代码需要大量的重构,像这样的并发错误很难查明。我的赌注是await Task.Run(() =&gt; AsyncScan());,这是错误的,因为AsyncScan()async,而Task.Run(...) 将在完成之前返回。

我的第二个猜测是m_ipsFound,它被称为共享状态。这意味着可能有许多线程同时对此进行读写。 List&lt;T&gt; 不是这个的数据类型。

另外,在方法的最后一行使用return 并不会增加可读性,async void 是一种被禁止的做法。始终使用async Task,即使您什么也不返回。你可以阅读更多关于this很好的答案。

【讨论】:

  • 谢谢。我可以访问受锁保护的m_ipsFound....我认为这可以防止线程访问该资源的问题...是吗?
猜你喜欢
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 2021-08-12
  • 1970-01-01
  • 2020-06-26
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多