【问题标题】:using ping in infinite loop在无限循环中使用 ping
【发布时间】:2023-11-30 20:58:01
【问题描述】:

我想持续检查仪器的连接性。这些仪器通过 LAN 连接。

我在无限循环中对它们执行 ping 操作。几秒钟后,我遇到了蓝屏,系统重新启动。

private async void checkingDevice()
{
    await Task.Run(() =>
    {
        do
        {
            Panel selectedPanel;
            multicastPing.send();
            foreach (var device in (Device[])Enum.GetValues(typeof(Device)))
            {
                selectedPanel = (Panel)this.Controls.Find((device).ToString(), true)[0];
                if (multicastPing.check(device.ToString()))
                    selectedPanel.BackgroundImage = Image.FromFile(Configs.imagesUrl + "enable\\" + selectedPanel.AccessibleName + ".png");
                else
                    selectedPanel.BackgroundImage = Image.FromFile(Configs.imagesUrl + "disable\\" + selectedPanel.AccessibleName + ".png");
            }
        } while (!flag);
        // TODO
        // delete instrument object after using in this snippet code
    });
} 

public bool send()
{
    foreach (var device in (Device[])Enum.GetValues(typeof(Device)))
        replyDictionary[device.ToString()] = sendDictionary[device.ToString()].Send(Configs.instrumentSpecification[device.ToString()].ip).Status;
    return true;
}

【问题讨论】:

  • 蓝屏是...奇怪但是:不要从另一个线程访问 UI(似乎是 WinForms)。另外:不要在每次 ping 时创建新图像...在循环外使用 Image.FromFile() 两次并重复使用它们(此外,如果状态没有改变,则无需更改图像)。
  • “蓝屏和系统被重新启动” - 你确定你不只是意味着你有一个异常窗口,上面写着cross thread operation exception?两者之间有很大的不同。纯 .NET 代码不太可能导致蓝屏
  • 不,我知道在主线程中创建的变量不能被另一个线程访问。这项工作导致了跨线程操作错误。但是我的应用程序导致系统遇到蓝屏并重新启动。当我从代码中删除相应的行 (multicastPing.send();) 时,这个问题就解决了。

标签: c# networking ping


【解决方案1】:

我的职业是测试和测量,做这种事情似乎很熟悉。因此,我将此作为一种方法来提供,看看我是否可以帮助您 ping 您的仪器堆栈。

蓝屏非常罕见,仅从个人经验来看,很可能是非常低级的 IO 内核驱动程序(以太网端口?)遇到了一些糟糕的事情。潜在地,循环似乎可以非常快速运行到超出缓冲区的程度?或者,病态地,您可能无意中将 UI 线程绑定到内核驱动程序的线程。哎哟。

我同意 cmets 关于使用 MethodInvoker 从任务中安全调用 UI 线程的观点。但我认为也许主要问题可能来自这个循环“尽可能快地运行”这一事实,这可能非常快。我模拟了类似这样的东西,它可以限制 ping,以便它们每秒发生一些有限的次数,并且它似乎工作正常。

一个安全线程化的任务,它执行外观而不会阻塞 UI 线程,看起来像这样:

CancellationTokenSource _cts = null;
SemaphoreSlim ssBusy = new SemaphoreSlim(1);
private void ExecMulticastPing()
{
    ssBusy.Wait();
    Task.Run(() =>
    {
        try
        {
            _cts = new CancellationTokenSource();

            do
            {
                List<Task<PingReply>> asyncPings = new List<Task<PingReply>>();

                // Sends out the pings in rapid succession to execute asynchronously in parallel
                foreach (var device in (Device[])Enum.GetValues(typeof(Device)))
                {
                    asyncPings.Add(Task.Run(() => SinglePingAsync(device, _cts.Token)));
                }

                // Waits for all the async pings to complete.
                Task.WaitAll(asyncPings.ToArray());

                // See if flag is already cancelled
                if (_cts.IsCancellationRequested) break;

                foreach (var device in (Device[])Enum.GetValues(typeof(Device)))
                {
                    SetPanelImage(device, asyncPings[(int)device].Result);
                }

                // I believe that it's very important to throttle this to
                // a reasonable number of repeats per second.
                Task.Delay(1000).Wait();
                BeginInvoke((MethodInvoker)delegate
                {
                    WriteLine(); // Newline
                });

            } while (!_cts.IsCancellationRequested); // Check if it's cancelled now
        }
        finally
        {
            ssBusy.Release();
        }
        BeginInvoke((MethodInvoker)delegate
        {
            WriteLine("CANCELLED");
        });
    });
}

...哪里...

const string URL_FOR_TEST = @"www.ivsoftware.com";

private PingReply SinglePingAsync(Device device, CancellationToken token)
{
    if(token.IsCancellationRequested)
    {
        return null;
    }
    Ping pingSender = new Ping();
    PingOptions options = new PingOptions()
    {
        DontFragment = true
    };
    PingReply reply = pingSender.Send(URL_FOR_TEST);
    BeginInvoke((MethodInvoker)delegate
    {
        if (reply.Status == IPStatus.Success)
        {
            WriteLine("Address: " + reply.Address.ToString());
            WriteLine("RoundTrip time: " + reply.RoundtripTime);
            WriteLine("Time to live: " + reply.Options.Ttl);
            WriteLine("Don't fragment: " + reply.Options.DontFragment);
            WriteLine("Buffer size: " + reply.Buffer.Length);
            WriteLine();
        }
        else
        {
            WriteLine("REQUEST TIMEOUT");
        }
    });
    return reply;
}

...和...

private void SetPanelImage(Device device, PingReply reply)
{
    BeginInvoke((MethodInvoker)delegate
    {
        WriteLine("Setting panel image for " + device.ToString() + " " + reply.Status.ToString() );
        Panel selectedPanel = (
            from Control unk in Controls
            where
                (unk is Panel) &&
                (unk.Name == device.ToString()) // ... or however you go about finding the panel...
            select unk as Panel
        ).FirstOrDefault();

        if (selectedPanel != null)
        {
            switch (reply.Status)
            {
                case IPStatus.Success:
                    // Set image for enabled
                    break;
                case IPStatus.TimedOut:
                    // Set image as disabled
                    break;
                default:
                    // Set image as disabled
                    break;
            }
        }
    });
}

这个10-second screen capture 展示了这种方法的成功,如果您认为这会有所帮助,您可以在我们的GitHub 存储库中浏览完整的示例代码。我还回答了一个不同但相关的问题here

【讨论】: