C# Task.Run使用问题解决

 更新时间:2025年12月12日 10:31:33   作者:WYXsdad11  
本文主要介绍了C# Task.Run使用问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

最近遇到一个问题:使用一个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的核心机制包含以下几个关键组成部分:

  1. 任务调度器(TaskScheduler)

    • 默认使用线程池任务调度器
    • 负责任务的排队和执行
    • 支持工作窃取算法提高CPU利用率
  2. 状态管理

    • 维护任务的生命周期状态(Created、WaitingToRun、Running、Completed等)
    • 通过状态机管理异步操作的执行流程
  3. 延续任务(Continuation)

    • 使用ContinueWithawait创建任务链
    • 自动处理任务间的依赖关系
  4. 线程池集成

    • 重用线程池线程,避免频繁创建销毁线程
    • 动态调整线程数量以适应负载变化
  5. 异常处理

    • 将异常封装在Task对象中
    • 支持统一的异常捕获和处理机制

线程池使用队列机制来管理任务:

  • 全局队列(Global Queue):接收所有新提交的任务
  • 本地队列(Local Queue):每个工作线程都有自己的本地队列,用于存放从全局队列中获取的任务

任务处理流程

  1. 任务提交‌:新任务进入全局队列
  2. 工作线程分配‌:空闲线程从全局队列获取任务执行
  3. 工作窃取(Work Stealing)‌:当线程本地队列为空时,可以从其他线程的本地队列"窃取"任务
  4. 线程管理‌:线程池根据任务数量动态调整工作线程数
  5. 任务调度‌:使用先进先出(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使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#实现钉钉消息推送过程

    C#实现钉钉消息推送过程

    本文介绍通过C#实现钉钉消息推送报警数据至群聊的方法,重点讲解机器人设置步骤(建群、添加自定义机器人、配置关键字和Webhook)及C#代码实现,支持文本、Markdown等消息类型,用于设备报警提醒场景
    2025-07-07
  • 如何在C#中使用Dapper ORM

    如何在C#中使用Dapper ORM

    这篇文章主要介绍了如何在C#中使用Dapper ORM,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-03-03
  • C#中string.Compare 比较两个字符串的字典顺序

    C#中string.Compare 比较两个字符串的字典顺序

    C#中string.Compare方法用于比较两个字符串的字典顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-07-07
  • C#隐藏主窗口的方法小结

    C#隐藏主窗口的方法小结

    这篇文章主要介绍了C#隐藏主窗口的方法,列举了C#隐藏窗口的三种常用方法,涉及C#窗体操作的常用技巧,需要的朋友可以参考下
    2016-03-03
  • 图形学之Unity渲染管线流程分析

    图形学之Unity渲染管线流程分析

    这篇文章主要介绍了图形学之Unity渲染管线流程的相关资料,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-01-01
  • C#语言中条件与&&与条件或||的区别

    C#语言中条件与&&与条件或||的区别

    这篇文章主要介绍了&&:逻辑与,前后条件同时满足表达式为真 ||:逻辑或,前后条件只要有一个满足表达式为真,下面结合案例给大家介绍,需要的朋友可以参考下
    2015-07-07
  • 使用C#实现网页内容保存为图片并生成压缩包

    使用C#实现网页内容保存为图片并生成压缩包

    这篇文章主要为大家详细介绍了如何使用C#实现网页内容保存为图片并生成压缩包,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02
  • Unity5.6大规模地形资源创建方法

    Unity5.6大规模地形资源创建方法

    这篇文章主要为大家详细介绍了Unity5.6大规模地形资源创建方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • C#实现协变和逆变案例

    C#实现协变和逆变案例

    这篇文章介绍了C#实现协变和逆变的案例,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-10-10
  • 在 C# 中使用 Span<T> 和 Memory<T> 编写高性能代码的详细步骤

    在 C# 中使用 Span<T> 和 Memory<

    在本文中,将会介绍 C# 7.2 中引入的新类型:Span 和 Memory,文章深入研究 Span<T> 和 Memory<T> ,并演示如何在 C# 中使用它们,需要的朋友可以参考下
    2022-08-08

最新评论