展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > C#TaskTaskFactory设置最大并行线程数的方法

C#TaskTaskFactory设置最大并行线程数的方法

LimitedConcurrencyLevelTaskScheduler继承TaskScheduler,通过链表存储待执行任务,设定最大并发数。QueueTask将任务加入链表尾部,若当前运行委托数小于最大值则递增计数并调用ThreadPool.UnsafeQueueUserWorkItem启动工作线程。工作线程循环从链表取出任务执行直至队列为空,减少运行计

1. LimitedConcurrencyLevelTaskScheduler 介绍

这个TaskScheduler,接触过.NET并发编程的同学应该不陌生——微软开源的一个任务调度器,代码本身确实不长,逻辑也算直白。不过,有一个问题值得琢磨:它到底是怎么实现并发数限制的?

C#TaskTaskFactory设置最大并行线程数的方法

先把源码贴出来,大家一起熟悉一下。

public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
    /// Whether the current thread is processing work items. 
    [ThreadStatic]
    private static bool _currentThreadIsProcessingItems;
    /// The list of tasks to be executed. 
    private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) 
                                                                       /// The maximum concurrency level allowed by this scheduler. 
    private readonly int _maxDegreeOfParallelism;
    /// Whether the scheduler is currently processing work items. 
    private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks) 
    ///  
    /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the 
    /// specified degree of parallelism. 
    ///  
    /// The maximum degree of parallelism provided by this scheduler. 
    public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
    {
        if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
        _maxDegreeOfParallelism = maxDegreeOfParallelism;
    }
    /// 
    /// current executing number;
    /// 
    public int CurrentCount { get; set; }
    /// Queues a task to the scheduler. 
    /// The task to be queued. 
    protected sealed override void QueueTask(Task task)
    {
        // Add the task to the list of tasks to be processed. If there aren't enough 
        // delegates currently queued or running to process tasks, schedule another. 
        lock (_tasks)
        {
            Console.WriteLine("Task Count : {0} ", _tasks.Count);
            _tasks.AddLast(task);
            if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
            {
                ++_delegatesQueuedOrRunning;
                NotifyThreadPoolOfPendingWork();
            }
        }
    }
    int executingCount = 0;
    private static object executeLock = new object();
    ///  
    /// Informs the ThreadPool that there's work to be executed for this scheduler. 
    ///  
    private void NotifyThreadPoolOfPendingWork()
    {
        ThreadPool.UnsafeQueueUserWorkItem(_ =>
        {
            // Note that the current thread is now processing work items. 
            // This is necessary to enable inlining of tasks into this thread. 
            _currentThreadIsProcessingItems = true;
            try
            {
                // Process all a vailable items in the queue. 
                while (true)
                {
                    Task item;
                    lock (_tasks)
                    {
                        // When there are no more items to be processed, 
                        // note that we're done processing, and get out. 
                        if (_tasks.Count == 0)
                        {
                            --_delegatesQueuedOrRunning;
                            break;
                        }
                        // Get the next item from the queue 
                        item = _tasks.First.Value;
                        _tasks.RemoveFirst();
                    }
                    // Execute the task we pulled out of the queue 
                    base.TryExecuteTask(item);
                }
            }
            // We're done processing items on the current thread 
            finally { _currentThreadIsProcessingItems = false; }
        }, null);
    }
    /// Attempts to execute the specified task on the current thread. 
    /// The task to be executed. 
    ///  
    /// Whether the task could be executed on the current thread. 
    protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // If this thread isn't already processing a task, we don't support inlining 
        if (!_currentThreadIsProcessingItems) return false;
        // If the task was previously queued, remove it from the queue 
        if (taskWasPreviouslyQueued) TryDequeue(task);
        // Try to run the task. 
        return base.TryExecuteTask(task);
    }
    /// Attempts to remove a previously scheduled task from the scheduler. 
    /// The task to be removed. 
    /// Whether the task could be found and removed. 
    protected sealed override bool TryDequeue(Task task)
    {
        lock (_tasks) return _tasks.Remove(task);
    }
    /// Gets the maximum concurrency level supported by this scheduler. 
    public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
    /// Gets an enumerable of the tasks currently scheduled on this scheduler. 
    /// An enumerable of the tasks currently scheduled. 
    protected sealed override IEnumerable GetScheduledTasks()
    {
        bool lockTaken = false;
        try
        {
            Monitor.TryEnter(_tasks, ref lockTaken);
            if (lockTaken) return _tasks.ToArray();
            else throw new NotSupportedException();
        }
        finally
        {
            if (lockTaken) Monitor.Exit(_tasks);
        }
    }
}

简单使用

下面是调用示例,非常简单:

static void Main(string[] args)
{
        TaskFactory fac = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(5));
        //TaskFactory fac = new TaskFactory();
        for (int i = 0; i < 1000; i++)
        {
            fac.StartNew(s => {
                Thread.Sleep(1000);
                Console.WriteLine("Current Index {0}, ThreadId {1}",s,Thread.CurrentThread.ManagedThreadId);
            }, i);
        }
        Console.ReadKey();
}

调用逻辑很清晰:用 LimitedConcurrencyLevelTaskScheduler 创建 TaskFactory,然后通过 StartNew 提交任务。从调试顺序可以看到,每次 StartNew 都会进入 QueueTask 方法。

/// Queues a task to the scheduler. 
    /// The task to be queued. 
    protected sealed override void QueueTask(Task task)
    {
        // Add the task to the list of tasks to be processed. If there aren't enough 
        // delegates currently queued or running to process tasks, schedule another. 
        lock (_tasks)
        {
            Console.WriteLine("Task Count : {0} ", _tasks.Count);
            _tasks.AddLast(task);
            if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
            {
                ++_delegatesQueuedOrRunning;
                NotifyThreadPoolOfPendingWork();
            }
        }
    }
    

QueueTask 的步骤很简单:把新任务追加到链表尾部,然后检查当前正在运行或已排队的委托数量(_delegatesQueuedOrRunning)是否小于设定的最大并发数。如果小于,就递增计数并调用 NotifyThreadPoolOfPendingWork 去启动一个工作线程。

但真正的疑问,恰恰出在这个 NotifyThreadPoolOfPendingWork 方法上。

private void NotifyThreadPoolOfPendingWork()
    {
        ThreadPool.UnsafeQueueUserWorkItem(_ =>
        {
            // Note that the current thread is now processing work items. 
            // This is necessary to enable inlining of tasks into this thread. 
            _currentThreadIsProcessingItems = true;
            try
            {
                // Process all a vailable items in the queue. 
                while (true)
                {
                    Task item;
                    lock (_tasks)
                    {
                        // When there are no more items to be processed, 
                        // note that we're done processing, and get out. 
                        if (_tasks.Count == 0)
                        {
                            --_delegatesQueuedOrRunning;
                            break;
                        }
                        // Get the next item from the queue 
                        item = _tasks.First.Value;
                        _tasks.RemoveFirst();
                    }
                    // Execute the task we pulled out of the queue 
                    base.TryExecuteTask(item);
                }
            }
            // We're done processing items on the current thread 
            finally { _currentThreadIsProcessingItems = false; }
        }, null);
    }

看这个方法的内部逻辑:它直接丢了一个死循环到线程池里,循环内不断从 _tasks 中取出任务执行,直到队列为空才退出循环。这看起来就像是一个“无限吞噬”的过程——一旦启动,就会把所有任务吃光,根本看不到任何限制并发数的机制。

唯一能扯上“限制”的地方,是 QueueTask 里那个 if 判断:只有当前执行线程数小于最大并发度时,才调用 NotifyThreadPoolOfPendingWork。但这似乎没什么用,因为一旦调用,这个工作线程就会一直跑,直到把队列清空。这样一来,并发度不就失控了吗?

那么问题来了:LimitedConcurrencyLevelTaskScheduler 到底是如何实现并发数限制的?

是不是哪里理解有偏差?比如,NotifyThreadPoolOfPendingWork 中 while 循环每次取任务时,会不会因为锁的竞争或其他机制而自然阻塞?但实际上锁只会保护 _tasks 的访问,并不控制线程数量。更关键的是,QueueTask 中的 if 条件保证了同时只有 _maxDegreeOfParallelism 个线程被启动,但每个线程都是“死循环”,这会不会导致任务被一个线程全部执行完,其他线程根本拿不到任务?

仔细想想,死循环本身并不占用多个线程——它只用当前这一个线程。但问题在于,当多个任务同时被提交时,QueueTask 可能被多次调用(来自不同的调用线程),而每次调用如果满足条件都会启动一个新的工作线程。假设并发数设为5,在任务提交的瞬间,如果同时有10个线程调用 QueueTask,前5个会启动工作线程,后5个不会。但前5个工作线程各自进入死循环,彼此独立地从 _tasks 中取任务——这确实实现了5个线程同时消费任务。真正的限制在于:不会启动超过5个工作线程。而死循环保证了每个工作线程会持续消费任务,而不是执行一个就退出,这样即便后续有新任务加入,也无需再启动新线程(因为现有工作线程还在循环中)。

换句话说,这个设计的精巧之处在于:工作线程采用“持续消费”模式,而不是“消费一次就结束”。QueueTask 中的 if 判断确保了最多只有 _maxDegreeOfParallelism 个工作线程存在,而这些线程会一直循环直到队列为空,从而实现了并发度的硬限制。

当然,这个实现有一个潜在的缺陷:如果任务生产速度远大于消费速度,工作线程会一直忙,但一旦队列为空,工作线程退出,后续新任务到来时,如果此时 _delegatesQueuedOrRunning 已经减到小于 _maxDegreeOfParallelism,就会重新启动新工作线程。这个计数更新是在 while 循环退出时(_tasks.Count == 0)进行的,所以是安全的。

所以,回过头来看,这个调度器对并发数的限制,本质上是通过控制“同时运行的工作线程数量”来实现的。虽然每个工作线程内部是个死循环,但死循环的线程数量是固定的,因此并发度也就固定了。

以上是个人理解,不知是否完全准确。如果有不同看法或者更深入的分析,欢迎交流讨论。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
精品专题 更多
本月促销

正软商城本月促销专区,汇集办公、设计、安全、影音、系统工具及AI软件等正版软件优惠活动,提供限时折扣、特价授权和优惠购买信息,活动库存及价格以页面实时展示为准。

装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。