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系统,请问如何确保请求按预期时间精准发送?
解决方案
核心原因分析
Task.Delay的精度限制:Linux系统默认定时器调度粒度约为10-15毫秒,当计算的等待时间小于这个粒度,或者系统负载较高时,Task.Delay无法精准触发,会出现延迟偏差。- 时间计算时机偏差:每个
SendRequest调用时才计算DateTime.UtcNow,如果任务启动有先后,后续任务的时间基准会偏移,导致等待时间计算错误。 - 代码潜在问题:
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
相关产品推荐
相关产品推荐

