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

C#如何精准按指定时间点向服务器发送多个请求

问题描述

我编写了如下C#代码:

public static async Task<Request> SendRequest(Client client, DateTime startTime, int offset)
{
    TimeSpan timeToWait = startTime - DateTime.UtcNow + TimeSpan.FromMilliseconds(offset);
    await Task.Delay(timeToWait);
    Console.Write(offset);
    Request request = client.sendRequest();
}

async static public Task<Request> SendMultiple(Client client, DateTime startTime)
{
    var offsets = new[] { -15, -10 };
    offsets.ForEach(offset => tasks.Add(SendRequest(client, startTime, offset)));
    Request[] result = await Task.WhenAll(tasks);
}

实际运行后打印结果如下:

08:59:59,985: -15
09:00:00,015: -10

可见第一个请求确实在9点前15毫秒发送,但第二个请求却在9点后15毫秒发送,而非预期的9点前10毫秒。我使用Ubuntu系统,请问如何确保请求按预期时间精准发送?


解决方案

核心原因分析

  1. Task.Delay的精度限制:Linux系统默认定时器调度粒度约为10-15毫秒,当计算的等待时间小于这个粒度,或者系统负载较高时,Task.Delay无法精准触发,会出现延迟偏差。
  2. 时间计算时机偏差:每个SendRequest调用时才计算DateTime.UtcNow,如果任务启动有先后,后续任务的时间基准会偏移,导致等待时间计算错误。
  3. 代码潜在问题:SendMultiple中tasks变量未初始化,需先声明为var tasks = new List<Task<Request>>();才能正常运行。

具体改进方案

1. 调整Linux系统调度精度(提升基础定时器精度)

临时生效:

sudo sysctl -w kernel.timer_migration=0
sudo sysctl -w kernel.sched_min_granularity_ns=1000000

永久生效:编辑/etc/sysctl.conf添加以下内容,再执行sudo sysctl -p:

kernel.timer_migration=0
kernel.sched_min_granularity_ns=1000000

此操作将系统调度粒度设置为1毫秒,为精准定时提供基础支持。

2. 修正时间计算与等待逻辑

提前计算每个任务的目标执行时间,改用循环等待的方式替代Task.Delay,确保到达目标时间后立即执行请求:

public static async Task<Request> SendRequest(Client client, DateTime targetTime)
{
    // 循环等待至目标时间,兼顾精度与CPU占用
    while (DateTime.UtcNow < targetTime)
    {
        // 短睡眠降低CPU消耗
        await Task.Delay(1);
    }
    
    Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss,fff}: {targetTime - DateTime.UtcNow.AddMilliseconds(-offset)}");
    return client.sendRequest();
}

async static public Task<Request[]> SendMultiple(Client client, DateTime startTime)
{
    var offsets = new[] { -15, -10 };
    var tasks = new List<Task<Request>>();
    
    foreach (var offset in offsets)
    {
        // 提前计算每个任务的精确目标时间
        var targetTime = startTime.AddMilliseconds(offset);
        tasks.Add(SendRequest(client, targetTime));
    }
    
    return await Task.WhenAll(tasks);
}

3. 优化线程池配置

避免线程池饥饿导致任务无法及时调度,程序启动时设置线程池最小线程数:

ThreadPool.SetMinThreads(Environment.ProcessorCount * 2, Environment.ProcessorCount * 2);

4. 验证与调试要点

  • 尽量在系统低负载时测试,高负载会影响调度精度
  • 始终使用DateTime.UtcNow,避免本地时区、夏令时带来的时间偏差
  • 日志中同时输出目标时间与实际执行时间,便于对比调试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 12:50:15