最近遇到一个问题:使用一个thread.timer的定时器每隔250毫秒输出一串数据,与此同时,有其他几个task在往数据库更新数据,这个过程比较耗时。因此这个数据库操作影响了定时器输出数据的操作,导致输出数据延时了。由此问题写了一个demo进行测试:
private void form1_load(object sender,eventargs e)
{
timercallbacl svcallback=batchquerysvdata;
system.threading.timer _timer=new system.threading.timer(svcallback,null,0,250);
}
public static async void batchquerysvdata(object state)
{
await task.run(()=>{
console.writeline(index++);
thread.sleep(200);
});
}
private void button1_clcik(object sender,eventargs e)
{
for(int i=0;i<=2;i++)
{
task.run(()=>{
thread.sleep(1000*10);
});
}
}
如上,当一个定时器的task每250毫秒输出数据时,此时增加一个耗时的点击事件的task,当频繁点击按钮时,定时器的task输出的数据会发生延迟。
由此了解了一下task及线程池的运行机制:
task的核心机制包含以下几个关键组成部分:
任务调度器(taskscheduler)
- 默认使用线程池任务调度器
- 负责任务的排队和执行
- 支持工作窃取算法提高cpu利用率
状态管理
- 维护任务的生命周期状态(created、waitingtorun、running、completed等)
- 通过状态机管理异步操作的执行流程
延续任务(continuation)
- 使用
continuewith或await创建任务链 - 自动处理任务间的依赖关系
- 使用
线程池集成
- 重用线程池线程,避免频繁创建销毁线程
- 动态调整线程数量以适应负载变化
异常处理
- 将异常封装在task对象中
- 支持统一的异常捕获和处理机制
线程池使用队列机制来管理任务:
- 全局队列(global queue):接收所有新提交的任务
- 本地队列(local queue):每个工作线程都有自己的本地队列,用于存放从全局队列中获取的任务
任务处理流程
- 任务提交:新任务进入全局队列
- 工作线程分配:空闲线程从全局队列获取任务执行
- 工作窃取(work stealing):当线程本地队列为空时,可以从其他线程的本地队列"窃取"任务
- 线程管理:线程池根据任务数量动态调整工作线程数
- 任务调度:使用先进先出(fifo)原则处理队列中的任务
这种设计实现了高效的负载均衡,避免了线程频繁创建销毁的开销,同时通过工作窃取机制提高了cpu利用率。
由此了解,上述代码的执行延迟的原因可能是:
定时器回调提交任务到线程池 → 任务进入全局队列 → 线程池分配可用线程执行 → 当长时间运行的数据库任务占用所有线程时,新的定时器任务只能在队列中等待 → 导致执行延迟。
然后进行一些验证工作:
排除定时器的原因:去掉定时器,使用while(true)不停创建task,结果仍然存在延迟问题。
private void form1_load(object sender,eventargs e)
{
batchquerysvdata(null);
}
public static async void batchquerysvdata(object state)
{
while(true)
{
await task.run(()=>{
console.writeline(index++);
thread.sleep(200);
});
}
}进一步验证,改用thread专用线程处理定时任务
private thread _timerthread;
private void form1_load(object sender, eventargs e)
{
_timerthread = new thread(() =>
{
while (true)
{
console.writeline(index++);
thread.sleep(250); // 精确控制间隔
}
})
{ isbackground = true };
_timerthread.start();
}
修改后果然没有延迟问题了。
问题原因总结:
1.双重排队:定时器回调本身就是在线程池处理,内部又使用task.run创建了另一个线程池任务
2.线程饥饿:长时间运行的数据库任务(10秒)占用线程池线程,导致定时器任务无法及时获取线程,且存在大量高并发任务提交影响程序性能和响应能力。
3.线程切换开销:频繁创建短生命周期任务引发线程切换开销增大,后续任务排队等待。
4.队列堆积:当线程池所有线程都被长时间任务占用时,新的定时器任务只能在队列中等待。
到此这篇关于c# task.run使用问题解决的文章就介绍到这了,更多相关c# task.run使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论