• 《CLR via C#》之线程处理——任务调度器


    《CLR via C#》之线程基础——任务调度器

    FCL提供了两个派生子TaskScheduler的类型:线程池任务调度器(thread pool task scheduler),和同步上下文任务调度器(synchronization context task scheduler)。默认情况下都使用线程池任务调度器。

    线程池任务调度器

    通过TaskScheduler的静态Default属性获得默认任务调度器的引用。

    设置线程池限制

    线程池永远不应该设置最大线程数,因为可能发生饥饿。虽然CLR提供了相关函数——ThreadPool静态方法(强烈不建议调用):GetMaxThreads,SetMaxThreads,GetMinThreads,SetMinThreads和GetAvailableThreads。
    CLR线程数的默认值大约是1000个线程,基本可以认为是无限制。因为在32位进程最大用户空间为2GB,加载完Win32和CLR等DLLs后,大约剩余1.5GB地址空间。每个线程都需要超过1MB的内存(用户模式栈,线程环境块TEB),所以最多大约有1360个线程。试图创建更多会抛出OutOfMemoryException。64位的进程提供8TB地址空间,理论上可以创建千百万个线程。

    如何管理工作者线程

    CLR的线程池

    • ThreadPool.QueueUserWorkItem方法和Timer类
      总是将工作项放到全局队列中。采用先入先出(FIFO)策略来由工作者线程处理这个队列。防止多个工作者线程同时从全局队列取工作项,增加了一个线程同步锁(有时会成为性能瓶颈)。
    • 默认TaskScheduler来调度Task对象
      非工作者线程调度Task时,总是将它添加到全局队列。工作者线程调度Task时,将它添加到本地队列。
      工作者线程获取Task步骤如下:
      • 首先从本地队列查找一个Task,以后入先出(LIFO)算法。由于工作者线程是唯一访问本地队列头的线程,所以无需同步锁。
      • 如果本地队列空了,会尝试从另一个工作者线程的本地队列尾部“偷”一个Task,这个过程要求一个线程同步锁。
      • 如果所有的本地队列都空了,那么工作者线程会使用FIFO算法,从全局队列提取一个工作项。如果全局队列也空了,那么工作者线程会进入睡眠状态,等待事情发生,如果睡眠时间太长,会自己醒了,并销毁自身以释放线程占用的资源。

    同步上下文任务调度器

    同步上下文任务调度器适合提供图形用户界面的应用程序。比如Form,WPF,Silverlight和Windows Store。它将所有任务都调度给GUI线程,使任务代码更新UI组件。通过执行TaskScheduler的静态方法FormCurrentSynchronizeContext来获得同步上下文任务调度器的引用。

    1. internal sealed class MyForm : Form
    2. {
    3. private readonly TaskScheduler m_syncContextTaskScheduler;
    4. public MyForm()
    5. {
    6. // 获得一个同步上下文任务调度器的引用
    7. m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    8. Text = "Synchronization Context Task Scheduler Demo";
    9. Visible = true; Width = 600; Height = 100;
    10. }
    11. private CancellationTokenSource m_cts;
    12. protected override void OnMouseClick(MouseEventArgs e)
    13. {
    14. if (m_cts != null)
    15. { // An operation is in flight, cancel it
    16. m_cts.Cancel(); 712 PART V Threading
    17. m_cts = null;
    18. }
    19. else
    20. { // 操作正则进行,取消它
    21. Text = "Operation running";
    22. m_cts = new CancellationTokenSource();
    23. // 默认任务调度器调度的Task,在线程池执行
    24. Task<Int32> t = Task.Run(() => Sum(m_cts.Token, 20000), m_cts.Token);
    25. // 下面的任务使用同步上下文任务调度器,在GUI线程上执行
    26. t.ContinueWith(task => Text = "Result: " + task.Result,
    27. CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
    28. m_syncContextTaskScheduler);
    29. t.ContinueWith(task => Text = "Operation canceled",
    30. CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
    31. m_syncContextTaskScheduler);
    32. t.ContinueWith(task => Text = "Operation faulted",
    33. CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
    34. m_syncContextTaskScheduler);
    35. }
    36. base.OnMouseClick(e);
    37. }
    38. }

    自定义TaskScheduler派生类

    微软在Parallel Extensions Extras包中提供了大量和任务相关的示例代码,比如:

    • IOTaskScheduler
      将任务排队给线程池的I/O线程而不是工作者线程。
    • LimitedConcurrencyLevelTaskScheduler
      这个任务调度器不允许超过n(一个构造器参数)个任务同时执行
    • OrderedTaskScheduler
      派生自LimitedConcurrencyLevelScheduler,n为1。
    • ThreadPerTaskScheduler
      为每个任务 创建单独的线程,不使用线程池。
    • PrioritizingTaskScheduler
      调用Prioritize指出一个Task在所有普通任务之前处理,Deprioritize相反。




  • 相关阅读:
    java实现前n项和,要求不使用循环、乘除法、判断标识
    java 线程池 带返回值
    java 多线程 数据通信
    jedis使用分布式锁
    记一次自定义管理工厂使用spring自动装载bean
    面试题玩数组
    记一次随便排序算法
    九九乘法表打印记一次al面试
    多线程操作共享变量顺序输出abc 记一次al面试题
    博客迁移
  • 原文地址:https://www.cnblogs.com/qianzi067/p/5819799.html
Copyright © 2020-2023  润新知