具有嵌套任务的 C# 代码比仅在顶层具有任务的相同代码运行速度慢

Posted

技术标签:

【中文标题】具有嵌套任务的 C# 代码比仅在顶层具有任务的相同代码运行速度慢【英文标题】:C# code with nested Tasks is running slower than same code with Tasks only at top layer 【发布时间】:2018-03-17 12:07:48 【问题描述】:

我遇到了一个问题,即场景 1 的运行速度比场景 2 慢,尽管场景 1 似乎应该运行得更快,因为它不仅在更高级别打开任务,而且在内部级别打开任务。有点像父子概念(我知道父/子实际上并没有发生,因为我相信任务都在同一级别运行)。场景 2 只是在更高级别创建任务,并且整体运行速度更快。我能想到的唯一一件事是运行更少的任务会更快,因此可以并行处理所有内容,而不是尝试打开更多任务,这很可能会导致在 4 核(8 线程)CPU 上等待。

我想弄清楚场景 2 是否总是会更快,或者是否有更好的方法来编写场景 1,使其实际上比场景 2 更快。

场景 1(有嵌套任务):

public void MainFunction() 
    IList<Task<ProductMaster>> tasks = new List<Task<ProductMaster>>();
    foreach (var x in products) 
    
        Task<ProductMaster> prodMaster = Task.Factory.StartNew<ProductMaster>(() => RunProductMasterCode(param1, param2));

        tasks.Add(prodMaster);   
    

    foreach (Task<ProductMaster> tsk in tasks)
    
        ProductMaster prodMaster = tsk.Result;
        // COMPLEX CODE HERE THAT RELIES ON tsk.Result
    


public ProductMaster RunProductMasterCode(int param1, int param2) 
    IList<Task<ProductSub>> tasks = new List<Task<ProductSub>>();
    foreach (var x in subProducts) 
    
        Task<ProductSub> prodSub = Task.Factory.StartNew<ProductSub>(() => RunProductSubCode(param1));

        tasks.Add(prodSub);
    

    foreach (Task<ProductSub> tsk in tasks)
    
        ProductSub prodSub = tsk.Result;
        // COMPLEX CODE HERE THAT RELIES ON tsk.Result
    


public ProductSub RunProductSubCode(int param1) 
    // COMPLEX CODE HERE

场景 2(有单层任务通知我没有在 RunProductMasterCode() 中创建任务):

public void MainFunction() 
    IList<Task<ProductMaster>> tasks = new List<Task<ProductMaster>>();
    foreach (var x in products) 
    
        Task<ProductMaster> prodMaster = Task.Factory.StartNew<ProductMaster>(() => RunProductMasterCode(param1, param2));

        tasks.Add(prodMaster);   
    

    foreach (Task<ProductMaster> tsk in tasks)
    
        ProductMaster prodMaster = tsk.Result;
        // COMPLEX CODE HERE THAT RELIES ON tsk.Result
    


public ProductMaster RunProductMasterCode(int param1, int param2) 
    foreach (var x in subProducts) 
    
        // NO THREADING HERE
        var prodSub = RunProductSubCode(param1);
    


public ProductSub RunProductSubCode(int param1) 
    // COMPLEX CODE HERE

【问题讨论】:

prodSubforeach 循环的本地。复杂的代码应该在循环内吗? @IanMcLaird 抱歉,我不得不对我的代码进行一些调整。我忘记表明我正在添加到任务列表中,然后在调用.Result 时循环遍历它。另外删除了您正在谈论的评论,因为它不应该出现在场景 2 中。 为什么不能将复杂代码作为每个任务的最后一步调用?复杂的代码是否要求所有任务都已完成? @IanMcLaird 每次看到复杂代码时都要纠正它依赖于完成所有任务。所以不,我不能把它称为每项任务的最后一步。 Task.WhenAll() 可以帮助解决这个问题,但我不确定你能否做得比现在更好。您可能刚刚发现进一步将任务分解为线程只会增加开销。 【参考方案1】:

使用StartNew 创建一个任务只是为了立即等待结果是什么都不做,同时增加了构造任务、调度线程池上的工作、等待该线程池线程被调度,然后等待的开销以便在工作完成后再次安排原始线程。

让原始线程同步执行实际工作与构建您立即等待的任务具有相同的好处,而无需任何额外(相当昂贵的)开销。

如果您的所有代码实际上都需要完全同步运行,那么您根本不应该使用任何任务,只需运行代码即可。

【讨论】:

感谢您指出这个缺陷。 RunProductMasterCode 和 RunProductSubCode 是两个非常复杂的函数,需要一段时间才能运行。假设我的主循环中有 20 个产品。我更喜欢利用服务器上的 CPU 内核并尽可能多地并行处理产品。如何调整我的代码使其不同步运行? @BlakeRivell 这将取决于他们实际在做什么,哪些操作可以安全地并行运行,哪些不能安全地并行运行,等等。它可能就像在你之前不处理结果一样简单已经开始了所有操作,或者可能非常复杂,具体取决于实际执行的操作。 抱歉,我的代码不正确。你现在可以看看吗。我忘了表明我正在添加到任务列表然后循环遍历它。我现在可能更有意义了。我很确定它在做正确的事情,因为场景 2 比在没有任何任务的情况下同步运行要快得多。 您现在有机会查看我的代码吗?很抱歉最初的结构搞砸了,所以我可以确切地看到你发布你所做的事情的原因。

以上是关于具有嵌套任务的 C# 代码比仅在顶层具有任务的相同代码运行速度慢的主要内容,如果未能解决你的问题,请参考以下文章

如何在所有站点上仅使用 css 获取具有相同数据属性的元素?

在 C# 中,如何处理具有多个线程/任务但有条件的大型文本文件?

如何区分具有相同名称的多个进程并杀死所有在 C# 中以我的 USERNAME 运行的进程?

使用顶层更改响应渲染

在 TabLayout 和 ViewPager2 中执行异步任务后更新具有相同布局的多个片段

具有状态“靠近”的任务者和几个具有几乎相同数量的蜂窝塔