每秒(或更短时间)检查 url 是否有效的最佳方法是什么,在 C# 窗口窗体应用程序 (.NET) 中使用 Task wait 或 ContinueWith

2024-03-26

我是 C# .Net 和 Visual Studio 2022 的新手 - 我想要实现的是每秒运行一个计时器来检查网站 url 是否有效/是否已启动。如果 url 可访问并且当前 WebView2 未显示该网站,那么它应该导航到该网站。如果它已经显示该网站,则它不应该执行任何其他操作。如果它显示该网站,但现在它不再有效,则 WebView 应导航到我的自定义错误页面。如果在自定义错误页面上网站再次可用,则应(重新)加载网站。 在我的特定场景中,我现在正在使 webView 加载 localhost (127.0.0.1)。我想不断检查网站的IP,如果它出现故障,则显示自定义错误,如果它回来,则显示该网站。

不确定我解释得很好。根据我所做的研究,我相信我需要 Task 并使用异步方法等待。

这是我当前的计时器和 checkurl 代码以及 navigtionstarted 和 navigationcompeted:

private void webView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
    timerCheckRSLCDURL.Enabled = false;
}

private void webView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
    if (e.IsSuccess)
    {
        Debug.WriteLine("JT:IsSuccess");
        ((Microsoft.Web.WebView2.WinForms.WebView2) sender).ExecuteScriptAsync("document.querySelector('body').style.overflow='hidden'");
    }
    else if (!e.IsSuccess)
    {
        Debug.WriteLine("JT:IsNOTSuccess");
        webView.DefaultBackgroundColor = Color.Blue;
        //webView.CoreWebView2.NavigateToString(Program.htmlString);
    }
    timerCheckRSLCDURL.Enabled = true;
}

private void timerCheckRSLCDURL_Tick(object sender, EventArgs e)
{
    Debug.WriteLine("Timer Fired! Timer.Enabled = " + timerCheckRSLCDURL.Enabled);
    CheckURL(Properties.Settings.Default.URL, Properties.Settings.Default.Port);
}

private async void CheckURL(string url, decimal port)
{
    timerCheckRSLCDURL = false;
    Program.isWebSiteUp = false;
    string webViewURL = BuildURL();
    Debug.WriteLine("Checking URL: " + webViewURL);

    try
    {
        var request = WebRequest.Create(webViewURL);
        request.Method = "HEAD";
        var response = (HttpWebResponse) await Task.Factory.FromAsync < WebResponse > (request.BeginGetResponse, request.EndGetResponse, null);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            Program.isWebSiteUp = true;
        }
    }
    catch (System.Net.WebException exception)
    {
        Debug.WriteLine("WebException: " + exception.Message);
        if (exception.Message.Contains("(401) Unauthorized"))
        {
            Program.isWebSiteUp = false;
        }
        else
        {
            Program.isWebSiteUp = false;
        } // This little block is unfinished atm as it doesn't really affect me right now
    }
    catch (Exception exception)
    {
        Debug.WriteLine("Exception: " + exception.Message);
        Program.isWebSiteUp = false;
    }

    if (Program.isWebSiteUp == true && webView.Source.ToString().Equals("about:blank"))
    {
        Debug.WriteLine("JT:1");
        Debug.WriteLine("isWebSiteUp = true, webView.Source = about:blank");
        webView.CoreWebView2.Navigate(webViewURL);
    }
    else if (Program.isWebSiteUp == true && !webView.Source.ToString().Equals(webViewURL))
    {
        Debug.WriteLine("JT:2");
        Debug.WriteLine("isWebSiteUp = true\nwebView.Source = " + webView.Source.ToString() + "\nwebViewURL = " + webViewURL + "\nWebView Source == webViewURL: " + webView.Source.ToString().Equals(webViewURL) + "\n");
        webView.CoreWebView2.Navigate(webViewURL);
    }
    else if (Program.isWebSiteUp == false && !webView.Source.ToString().Equals("about:blank"))
    {
        Debug.WriteLine("JT:3");
        Debug.WriteLine("This SHOULD be reloading the BSOD page!");
        webView.CoreWebView2.NavigateToString(Program.htmlString);
    }
}

private string BuildURL()
{
    string webViewURL;
    string stringURL = Properties.Settings.Default.URL;
    string stringPort = Properties.Settings.Default.Port.ToString();
    string stringURLPORT = $ "{stringURL}:{stringPort}";
    if (stringPort.Equals("80"))
    {
        webViewURL = stringURL;
    }
    else
    {
        webViewURL = stringURLPORT;
    }
    if (!webViewURL.EndsWith("/"))
    {
        webViewURL += "/";
    }
    //For now, the URL will always be at root, so don't need to worry about accidentally
    //making an invalid url like http://example.com/subfolder/:port 
    //although potentially will need to address this at a later stage

    Debug.WriteLine("BuildURL returns: " + webViewURL);

    return webViewURL;
}

因此计时器每 1000 毫秒(1 秒)触发一次,因为我需要主动检查 URL 是否仍然存在。我认为我控制计时器的方式是错误的 - 我想有更好的方法,但我想做的是......

  • 每 1 秒检查一次网站 URL
  • 为了避免重复相同的异步任务,我尝试禁用计时器,以便在异步 checkurl 运行时它不会再次触发
  • 一旦检查 url 的 async/await 任务完成,应重新启用计时器以继续监视网站 url 是否仍然存在
  • 如果网站关闭,它应该显示我的自定义错误页面(称为 BSOD),这是从资源加载并“存储”在 Program.htmlString 中的一些超级基本 html
  • 如果网站关闭,并且 webview 已经显示 BSOD,则 webview 不应该执行任何操作。计时器应继续监视 URL。
  • 如果网站已启动并且 Web 视图显示 BSOD,则它应该导航到已检查的已启动 URL。如果网站已启动,并且 Web 视图已显示该网站,则 Web 视图不应该执行任何操作。计时器应继续监视 URL。

从其他研究来看,我知道我不应该使用private async void- 例如不应该将其用作空白。但我还没有弄清楚/理解正确的方法来做到这一点

在立即窗口中,看起来webView_NavigationCompleted立即被触发两次(有时甚至几次),因为立即窗口输出将显示JT:IsSuccess or JT:IsNOTSuccess快速连续地重复几次。这正常吗?我假设那里有些东西不正确。

主要问题似乎是由于计时器只有 1 秒。例如,如果我将计时器更改为每 30 秒触发一次,它似乎可以正常工作,但是当每秒触发一次时(我什至可能在某些时候需要的时间少于该时间),它就无法真正按预期工作。例如,有时 BSOD 根本无法加载,webView_NavigationCompleted快速连续多次起火等

有人可以帮助我使这段代码变得更好和正确吗? 我搜索了无数的网站等,虽然有一些很好的信息,但其中一些似乎势不可挡/可以说技术性太强。我必须先查一下“前因”的含义,因为它对我来说是一个全新的词! :捂脸:

提前谢谢了


这个答案将重点关注任务计时器循环来回答您问题的特定部分“每秒检查一次 url 是否有效“。关于如何执行实际 Ping 有很多答案(例如C# 如何检查网站是否在线 https://stackoverflow.com/q/7523741/5438626)这是 Microsoft 文档Ping https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=net-6.0如果你选择走那条路。

由于为 ping 请求设置 120 秒的超时值并不罕见,因此人们质疑在稳定的一秒滴答声中是否有任何值。我的建议是,以下做法更有意义:

  1. 制作后台线程
  2. 在后台线程上执行同步 ping(等待结果)。
  3. 将 ping 结果编组到 UI 线程上以执行您布置的其他任务。
  4. 在执行下一个 ping 之前,在后台线程上同步等待 Task.Delay。

以下是我个人在自己的生产代码中执行此操作的方法:

void execPing()
{
    Task.Run(() => 
    {
        while (!DisposePing.IsCancellationRequested)
        {
            var pingSender = new Ping();
            var pingOptions = new PingOptions
            {
                DontFragment = true,
            };
            // https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=net-6.0#examples
            // Create a buffer of 32 bytes of data to be transmitted.
            string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
            byte[] buffer = Encoding.ASCII.GetBytes(data);
            int timeout = 120;
            try
            {
                // https://stackoverflow.com/a/25654227/5438626
                if (Uri.TryCreate(textBoxUri.Text, UriKind.Absolute, out Uri? uri)
                    && (uri.Scheme == Uri.UriSchemeHttp || 
                    uri.Scheme == Uri.UriSchemeHttps))
                {
                    PingReply reply = pingSender.Send(
                        uri.Host, 
                        timeout, buffer, 
                        pingOptions);
                    switch (reply.Status)
                    {
                        case IPStatus.Success:
                            Invoke(() => onPingSuccess());
                            break;
                        default:
                            Invoke(() => onPingFailed(reply.Status));
                            break;
                    }
                }
                else
                {
                    Invoke(() => labelStatus.Text = 
                        $"{DateTime.Now}: Invalid URI: try 'http://");
                }
            }
            catch (Exception ex)
            {
                // https://stackoverflow.com/a/60827505/5438626
                if (ex.InnerException == null)
                {
                    Invoke(() => labelStatus.Text = ex.Message);
                }
                else
                {
                    Invoke(() => labelStatus.Text = ex.InnerException.Message);
                }
            }
            Task.Delay(1000).Wait();
        }
    });
}

对我有用的是在创建主窗口句柄时初始化它:

protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);
    if (!(DesignMode || _isHandleInitialized))
    {
        _isHandleInitialized = true;
        execPing();
    }
}
bool _isHandleInitialized = false;

Where:

private void onPingSuccess()
{
    labelStatus.Text = $"{DateTime.Now}: {IPStatus.Success}";
    // Up to you what you do here
}

private void onPingFailed(IPStatus status)
{
    labelStatus.Text = $"{DateTime.Now}: {status}";
    // Up to you what you do here
}

public CancellationTokenSource DisposePing { get; } = new CancellationTokenSource();

示例404:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

每秒(或更短时间)检查 url 是否有效的最佳方法是什么,在 C# 窗口窗体应用程序 (.NET) 中使用 Task wait 或 ContinueWith 的相关文章

随机推荐

  • Swift 使滚动视图内容居中

    当用户当前位置移至屏幕框架之外时 我尝试将滚动视图视图居中并移动框架 目前我有一个 PDF 正在显示用户的当前位置 我正在计算框架和滚动视图的 ZoomScale 以在 PDF 视图上显示当前位置 我已经实现了这个功能 它运行完美 当用户移
  • 从 XML 绑定表达式调用控制器函数

    使用 OpenUI5 SAPUI5 根据文档XML 绑定表达式 https help sap com saphelp nw74 helpdata en da f6852a04b44d118963968a1239d2c0 content ht
  • Hibernate注释或XML配置

    我开始了一个新项目休眠 http en wikipedia org wiki Hibernate 28Java 29 Hibernate 注释是更好的选择还是 Hibernate XML 映射更好 我已经使用 Hibernate 和 XML
  • android 11 上 imageCapture 用例的camerax“未绑定到有效相机”

    我一直收到错误 未绑定到有效相机 当尝试实现 imageCapture 用例时 我正在按照camerax教程在Java中实现该功能here https codelabs developers google com codelabs came
  • 均匀分布图像库

    我有一个图像库 每个图像都有相同的宽度和高度 比如 10px 我希望它们间隔开 以便一行中有 5 个 并且它们在所有边上彼此间隔 5 像素 容器 div 具有 5px 内边距 有多个行 我的问题是 如果我给每个图像留出 5px 的左边距 那
  • 如何在 OS X 上使用键盘触发 VS Code IntelliSense?

    All the docs keep referring https code visualstudio com Docs editor debugging to Space to launch IntelliSense so as to g
  • 如何在 PHP 中调试多个会话?

    我正在开发一个项目 其中应用程序在开发模式下通过网络创建对同一应用程序的 Web 服务请求 以获得虚假数据和更好的开发环境 有时 当我调试时 我发现很难做到这一点 因为当您在同一个 PHP 安装上同时有两个调试会话时 XDebug 似乎不起
  • window.scrollTo 不滚动到提供的 id

    我正在处理一个文档文件 当单击 id about 的锚点时 我想滚动到 id projectpage 的所需部分 a class nav link page scroll href About Me a section 我尝试使用 a ab
  • 如何在发布过程中跳过文件夹删除?

    我无法做到这一点 因此从 Visual Studio 发布不会删除服务器网站上的 App Data 文件夹 但我也希望它继续删除所有文件 该文件夹除外 以保持目录 干净 我已经在 csproj pubxml 中尝试过这个 以及它的更改 有一
  • iOS 4 构建 SDK

    我昨天安装了 iOS 4 SDK 我之前安装过 3 1 2 3 1 3 和 3 2 SDK 自从安装了 iOS 4 SDK 以来 我在 Xcode 中只有 3 2 和 4 0 可用 当我加载针对旧版本 例如 3 1 2 的 Xcode 项目
  • Actionscript 3 数学不一致

    我正在尝试在 Flex actionscript 3 中构建一个计算器 但使用 Math 类得到了一些奇怪的结果 trace 1 4 4 should be 1 but it is 0 9999999999999999 trace 1 5
  • 选择按午夜时间分组的数据

    我有一个像这样的表 ID TIMEVALUE 1 06 07 15 06 43 01 000000000 2 06 07 15 12 17 01 000000000 3 06 07 15 18 21 01 000000000 4 06 07
  • 使用recvfrom()和sendto()发送结构

    我在用C语言这是一个共同平台对于服务器和客户端 我有一个特定类型的结构 我想将其从服务器发送到客户端 For e g 服务器代码 necessary declarations struct hostent hp hp gethostbyna
  • MySQL 时区查询

    我有两个字段 time scan start and time scan end 时间戳字段 当我使用 PDO 打开与 MySQL 的连接时 我使用SET NAMES utf8 time zone 0 00 现在 当我在 MySQL 中执行
  • 多方面散点图ggplot2中不同的x和y轴尺度

    我用过lemon封装有ggplot2使用以下代码绘制带有回归和置信区间线的多方面散点图 library tidyverse library lemon Plotting ggplot data calibration aes Observe
  • dask 中不支持项目分配的解决方法

    我正在尝试将我的代码库从 numpy 数组转换为 dask 因为我的 numpy 数组超出了Memory Error限制 但是 我发现可变数组的功能尚未实现dask arrays所以我得到了 NotImplementedError Item
  • 谁能给我解释一下 C++ 异常规范吗?

    谁能解释一下 C 中使用的异常规范 它们什么时候使用 我很少看到它在代码中使用 使用异常规范的优点和缺点 优点 缺点 是什么 它们什么时候使用 我很少看到它在代码中使用 希望永远不会 因为它们在明年标准化的下一版本 C 中已被弃用 使用异常
  • 如何修复错误“您可能需要适当的加载程序来处理此文件类型”

    我有一个全新的 Laravel 安装 关于使用编译文件npm run dev VUE我收到文件错误 您可能需要适当的加载程序来处理此文件类型 当前没有配置加载程序来处理此文件 Laravel 版本 8 12 包 json devDepend
  • Facebook 评论镜像不起作用

    我一步一步做了这些 创建一个新的 Facebook 应用程序 应用程序 ID 544557495732050 创建一个新的 Facebook 页面 https www facebook com Pasha Electronic 502844
  • 每秒(或更短时间)检查 url 是否有效的最佳方法是什么,在 C# 窗口窗体应用程序 (.NET) 中使用 Task wait 或 ContinueWith

    我是 C Net 和 Visual Studio 2022 的新手 我想要实现的是每秒运行一个计时器来检查网站 url 是否有效 是否已启动 如果 url 可访问并且当前 WebView2 未显示该网站 那么它应该导航到该网站 如果它已经显