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

WinForms应用中基于Timer的可重试地理编码请求与CancellationToken处理

实现Windows Forms地址输入后延迟地理编码并支持取消旧请求

这个需求我之前在Windows Forms项目里落地过,核心是用定时器实现输入后的延迟触发,同时借助取消令牌机制中断正在进行的旧请求,下面是完整的实现方案:

核心思路

  • 用System.Timers.Timer设置延迟(比如3秒),用户每次修改地址字段时重置定时器
  • 用CancellationTokenSource管理HTTP请求的生命周期,用户修正输入时立即取消当前请求
  • 注意线程安全:System.Timers.Timer的事件在后台线程触发,访问UI控件必须切换回UI线程

具体实现步骤

1. 定义类级变量

在Form类中声明需要的核心对象,用于线程同步和请求管理:

private System.Timers.Timer _geocodeTimer;
private CancellationTokenSource _currentCts;
private readonly object _timerLock = new object(); // 用于多线程操作的同步锁

2. 初始化定时器

在Form的Load事件或构造函数中完成定时器的配置:

private void AddressForm_Load(object sender, EventArgs e)
{
    // 设置延迟时间(3000毫秒=3秒,可根据需求调整)
    _geocodeTimer = new System.Timers.Timer(3000);
    _geocodeTimer.AutoReset = false; // 只触发一次,每次输入变化手动重启
    _geocodeTimer.Elapsed += GeocodeTimer_Elapsed; // 绑定触发事件
}

3. 监听地址字段的输入变化

给所有地址输入框(街道、城市、州、邮编)绑定TextChanged事件,每次输入变化时重置定时器并取消旧请求:

private void AddressField_TextChanged(object sender, EventArgs e)
{
    lock (_timerLock)
    {
        // 取消当前正在进行的请求(如果存在)
        _currentCts?.Cancel();
        // 停止当前定时器
        _geocodeTimer.Stop();
        
        // 创建新的取消令牌源,用于后续的请求
        _currentCts = new CancellationTokenSource();
        // 重启定时器,等待用户停止输入后触发地理编码
        _geocodeTimer.Start();
    }
}

4. 定时器触发时调用地理编码API

实现定时器的Elapsed事件处理函数,在这里完成API调用和UI更新:

private async void GeocodeTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    CancellationToken token;
    lock (_timerLock)
    {
        if (_currentCts == null) return;
        token = _currentCts.Token;
    }

    try
    {
        // 从UI控件获取地址信息(必须用Invoke切换回UI线程)
        string street = Invoke(new Func<string>(() => txtStreet.Text.Trim()));
        string city = Invoke(new Func<string>(() => txtCity.Text.Trim()));
        string state = Invoke(new Func<string>(() => txtState.Text.Trim()));
        string zip = Invoke(new Func<string>(() => txtZip.Text.Trim()));

        // 验证地址完整性,避免无效请求
        if (string.IsNullOrEmpty(street) || string.IsNullOrEmpty(city) || string.IsNullOrEmpty(state))
        {
            Invoke(new Action(() => lblStatus.Text = "地址信息不完整,请补充"));
            return;
        }

        // 调用US Census地理编码API
        var geocodeResult = await CallUSCensusGeocodeApi(street, city, state, zip, token);

        // 更新UI显示结果(切换回UI线程)
        Invoke(new Action(() =>
        {
            lblLatitude.Text = $"纬度: {geocodeResult.Latitude:F6}";
            lblLongitude.Text = $"经度: {geocodeResult.Longitude:F6}";
            lblStatus.Text = "地理编码完成";
        }));
    }
    catch (OperationCanceledException)
    {
        // 请求被用户输入中断,无需额外处理
        Invoke(new Action(() => lblStatus.Text = "请求已取消"));
    }
    catch (Exception ex)
    {
        // 处理API调用错误
        Invoke(new Action(() => lblStatus.Text = $"编码失败: {ex.Message}"));
    }
    finally
    {
        lock (_timerLock)
        {
            // 释放取消令牌源资源,避免内存泄漏
            _currentCts?.Dispose();
            _currentCts = null;
        }
    }
}

5. 实现US Census地理编码API调用

编写异步方法调用US Census的地理编码API,支持取消令牌:

private async Task<GeocodeResult> CallUSCensusGeocodeApi(string street, string city, string state, string zip, CancellationToken token)
{
    using (var client = new HttpClient())
    {
        // 构建API请求URL,注意转义特殊字符
        string apiUrl = $"https://geocoding.geo.census.gov/geocoder/locations/address" +
                        $"?street={Uri.EscapeDataString(street)}" +
                        $"&city={Uri.EscapeDataString(city)}" +
                        $"&state={Uri.EscapeDataString(state)}" +
                        $"&zip={Uri.EscapeDataString(zip)}" +
                        $"&benchmark=Public_AR_Current&format=json";

        // 发送GET请求并传入取消令牌
        using (var response = await client.GetAsync(apiUrl, token))
        {
            response.EnsureSuccessStatusCode(); // HTTP状态码非200时抛出异常
            string jsonResponse = await response.Content.ReadAsStringAsync(token);

            // 解析JSON响应,模型类对应US Census的返回结构
            var apiResponse = JsonSerializer.Deserialize<USCensusGeocodeResponse>(jsonResponse);
            
            // 提取第一个匹配结果的经纬度
            var firstMatch = apiResponse?.Result?.AddressMatches?.FirstOrDefault();
            return new GeocodeResult
            {
                Latitude = firstMatch?.Coordinates?.Y ?? 0,
                Longitude = firstMatch?.Coordinates?.X ?? 0
            };
        }
    }
}

// 用于解析API响应的模型类
public class GeocodeResult
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

public class USCensusGeocodeResponse
{
    public USCensusResult Result { get; set; }
}

public class USCensusResult
{
    public List<USCensusAddressMatch> AddressMatches { get; set; }
}

public class USCensusAddressMatch
{
    public USCensusCoordinates Coordinates { get; set; }
}

public class USCensusCoordinates
{
    public double X { get; set; } // 经度
    public double Y { get; set; } // 纬度
}

关键注意事项

  • 线程安全:System.Timers.Timer的Elapsed事件在后台线程执行,所有对UI控件的操作必须用Invoke或BeginInvoke切换回UI线程
  • 资源释放:每次请求完成或取消后,必须Dispose掉CancellationTokenSource,避免内存泄漏
  • AutoReset设置:将定时器的AutoReset设为false,确保每次输入变化只会触发一次地理编码请求
  • 输入验证:在调用API前验证地址字段的完整性,减少无效请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:12:59