You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms .NET 秒级URL检测与WebView2跳转逻辑实现方案

C# WinForms WebView2 短间隔URL可用性检测实现方案

异步选型直接用async/await即可,ContinueWith是早期TPL底层写法,需要手动配置线程同步上下文,WinForms场景下很容易触发跨线程操作UI的异常,新手不需要使用。await会自动帮你完成线程切换,代码逻辑和同步写法顺序一致,不需要接触你提到的antecedent(ContinueWith语法里指代前置任务的参数名)这类概念。


现有代码核心问题

  • 基础语法笔误:CheckURL方法里写的timerCheckRSLCDURL = false;是直接给Timer控件对象赋值布尔值,根本没有修改Enabled属性,等于完全没做防重入,1秒间隔下会堆积大量检测任务
  • 异步用法不规范:只有事件处理器允许用async void,普通业务方法用async void会导致异常无法捕获,直接造成进程崩溃
  • WebView2事件理解错误:NavigationCompleted会在页面iframe、重定向、静态资源加载时多次触发,在这个事件里反复启停Timer会直接导致Timer状态混乱
  • 状态判断逻辑错误:用about:blank判断是否在错误页完全不准,NavigateToString加载的自定义HTML不是about:blank,会导致逻辑反复触发跳转,错误页永远加载不完
  • 请求未加超时:旧的HttpWebRequest默认超时是100秒,1秒间隔下请求还没返回就会堆积大量排队任务,直接造成逻辑卡死

可直接落地的修正代码

用SemaphoreSlim做内存级重入锁,比靠启停Timer防重入可靠100%,不会因为事件多次触发导致锁失效,哪怕后续把检测间隔调到100ms也不会出现任务堆积。

第一步:定义Form类级别全局变量

// 放在Form类的最开头
private const int CheckIntervalMs = 1000;
private const int RequestTimeoutMs = 800; // 检测请求超时设为800ms,留200ms给UI操作
private readonly SemaphoreSlim _checkLock = new SemaphoreSlim(1,1); // 保证同时只有1个检测任务运行
private string _targetUrl = string.Empty;
private bool _isOnErrorPage = false; // 明确标记当前是否在错误页,不需要靠解析Source判断

第二步:配置全局HttpClient

在Program.cs里添加静态全局HttpClient实例,不要每次检测都新建,避免端口耗尽:

// Program类内添加
public static readonly HttpClient HttpClient = new HttpClient()
{
    Timeout = TimeSpan.FromMilliseconds(1000)
};

第三步:修正初始化逻辑

在窗体Load事件里完成WebView2和Timer初始化,不要在设计器里提前开启Timer:

private async void MainForm_Load(object sender, EventArgs e)
{
    // 等待WebView2初始化完成再开启检测
    await webView.EnsureCoreWebView2Async();
    _targetUrl = BuildURL();
    
    // 配置Timer
    timerCheckRSLCDURL.Interval = CheckIntervalMs;
    timerCheckRSLCDURL.Tick += timerCheckRSLCDURL_Tick;
    timerCheckRSLCDURL.Start();
}

第四步:修正WebView2导航事件

只处理主文档的导航事件,忽略iframe、静态资源触发的重复回调:

private void webView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
    // 只有主文档导航才更新状态
    if (e.NavigationKind == CoreWebView2NavigationKind.Document)
    {
        _isOnErrorPage = false;
    }
}

private void webView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
    // 非主框架导航直接忽略
    if (webView.CoreWebView2.MainFrameUri == null) return;
    
    if (e.IsSuccess)
    {
        Debug.WriteLine("主文档加载成功");
        webView.ExecuteScriptAsync("document.querySelector('body').style.overflow='hidden'");
        // 加载的是目标地址时更新错误页标记
        if (webView.CoreWebView2.MainFrameUri.Equals(_targetUrl, StringComparison.OrdinalIgnoreCase))
        {
            _isOnErrorPage = false;
        }
    }
    else
    {
        Debug.WriteLine($"主文档加载失败,错误码:{e.WebErrorStatus}");
        webView.DefaultBackgroundColor = Color.Blue;
    }
}

第五步:修正Timer触发逻辑

事件处理器用async void是符合异步规范的:

private async void timerCheckRSLCDURL_Tick(object sender, EventArgs e)
{
    // 如果锁已经被占用,直接跳过本次检测,不排队等待
    if (!_checkLock.Wait(0)) return;
    
    try
    {
        timerCheckRSLCDURL.Stop();
        bool isSiteUp = await CheckUrlReachableAsync(_targetUrl);
        
        if (isSiteUp)
        {
            // 站点可达时,不在目标页就跳转
            bool isOnTargetPage = webView.CoreWebView2.MainFrameUri?.Equals(_targetUrl, StringComparison.OrdinalIgnoreCase) ?? false;
            if (_isOnErrorPage || !isOnTargetPage)
            {
                Debug.WriteLine("站点可达,跳转至目标地址");
                webView.CoreWebView2.Navigate(_targetUrl);
            }
        }
        else
        {
            // 站点不可达时,不在错误页就跳转
            if (!_isOnErrorPage)
            {
                Debug.WriteLine("站点不可达,跳转至自定义错误页");
                _isOnErrorPage = true; // 先设标记,防止重复触发跳转
                webView.CoreWebView2.NavigateToString(Program.htmlString);
            }
        }
    }
    finally
    {
        // 无论检测成功失败,最后都释放锁、重启Timer
        _checkLock.Release();
        timerCheckRSLCDURL.Start();
    }
}

第六步:修正URL检测方法

方法返回Task<bool>,符合异步规范,添加超时控制:

private async Task<bool> CheckUrlReachableAsync(string url)
{
    try
    {
        using var cts = new CancellationTokenSource(RequestTimeoutMs);
        // 发HEAD请求只拿响应头,检测速度更快
        var response = await Program.HttpClient.SendAsync(
            new HttpRequestMessage(HttpMethod.Head, url), 
            cts.Token
        );
        // 所有2xx状态码都判定为可达,不单独判断200
        return response.IsSuccessStatusCode;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"URL检测失败:{ex.Message}");
        return false;
    }
}

第七步:修正URL拼接逻辑

用内置UriBuilder处理地址拼接,避免手动拼字符串出bug:

private string BuildURL()
{
    var uriBuilder = new UriBuilder(Properties.Settings.Default.URL)
    {
        Port = (int)Properties.Settings.Default.Port
    };
    // 自动处理80端口默认值、末尾斜杠问题
    return uriBuilder.Uri.ToString();
}

效果说明

  • 严格按照1秒间隔执行检测,检测任务运行时不会重复触发
  • 站点不可达时只会跳转一次错误页,不会重复加载
  • 站点恢复时只会跳转一次目标页,不会重复导航
  • 所有UI操作都在主线程执行,不会出现跨线程异常

内容的提问来源于stack exchange,提问作者IAmOrion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:18:25