C#《CLLacrosse Via C# 第1版》笔记之(十八) – 线程池

器重内容:

2. 做实线程质量

线程即使轻量(和经过相比较),但是到底也包罗了部分消息(可参见上一篇中的线程开支),所以假诺多了也会消耗过多系统能源。

而经过主线程来创立子线程时,主线程的上下文信息还得拷贝一份再传入子线程,比如上面的例子中新建了九贰十二个线程,就得将上下文消息拷贝九十六次。

在稍微景况下,子线程并没有用到主线程的上下文新闻,此时,大家就能够透过阻止上下文音讯的流淌(主线程–>子线程)来拉长线程的质量。

 

上边包车型地铁事例演示怎么样堵住和死灰复燃上下文音信的传递。

using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");
        CallContext.LogicalSetData("Info", "Main Thread's Context info.");

        // 阻止上下文的传递
        ExecutionContext.SuppressFlow();
        ThreadPool.QueueUserWorkItem(ThreadMethod, "Thread 1");

        // 恢复上下文的传递
        ExecutionContext.RestoreFlow();
        ThreadPool.QueueUserWorkItem(ThreadMethod, "Thread 2");

        Console.ReadKey(true);
    }

    private static void ThreadMethod(object state)
    {
        Console.WriteLine(state + "'s Context Info is : " + CallContext.LogicalGetData("Info"));
    }
}

运行结果如下:

C# 1

 

3. 撤废运转中的线程

在大家实际利用线程时,应当尽大概使用线程池来布局线程,幸免直接new多少个线程。

  • 决定财富消耗
  • 增进线程质量
  • 收回运行中的线程 

3.1 撤除线程

对此长日子运作的线程,借使不提供裁撤操作,那么它就会间接占有系统财富,直至运维成功。

这样的用户体验很不佳,所以对于有大概运营十分长日子的线程,应该提供撤除的操作供用户挑选。

线程的废除第②接纳CancellationTokenSource。

上边通过例子来演示怎么样裁撤1个线程的运作。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");
        CancellationTokenSource cts = new CancellationTokenSource();
        ThreadPool.QueueUserWorkItem(o => ThreadMethod(cts.Token));

        Console.WriteLine("Press any key to cancel.");
        Console.ReadKey(true);

        // 取消线程的操作
        cts.Cancel();

        Console.ReadKey(true);
    }

    private static void ThreadMethod(CancellationToken token)
    {
        do
        {
            // 线程取消前一直运行
            Console.WriteLine("Now is : {0}", DateTime.Now.ToString("HH:mm:ss"));
            Thread.Sleep(1000);
        } while (!token.IsCancellationRequested);

        Console.WriteLine("This thread is cancelled!");
    }
}

只要输入任意键就能够收回线程。

 

1. 操纵财富消耗

线程池(ThreadPool)运营线程的方法很简短,和上一篇直接new三个线程类似,也有带参数和不带参数三种。

public static bool QueueUserWorkItem(WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callBack, object state);

 

里面包车型客车WaitCallback委托定义如下:

public delegate void WaitCallback(object state);

 

那么,线程池是何许有效的控制系统能源的损耗的吧?

它的法则卓殊简单:

  • 线程池中保证七个呼吁队列,当应用程序有异步的伸手时,将此恳请(比如请求A)发送到线程池。
  • 线程池将请求A放入请求队列中,然后新建贰个线程(比如线程A)来拍卖请求A。
  • 请求A处理完了后,线程池不会销毁线程A,而是使用线程A来处理请求队列中的下一个请求(比如请求B)。
  • 当呼吁过多时,线程池才会再新建一些线程来加速处理请求队列中的请求。(注1
  • 当请求队列为空时,线程池会销毁一些有空时间比较长的线程。(注2

注1:保障全体的央浼由少量线程处理,收缩系统财富的消耗,同时减少了线程新建,销毁的次数。

注2:空闲时间多少长度才销毁线程是由CLCRUISER决定的,区别版本的CL帕杰罗这几个时间恐怕不一样。

 

上边通过3个例子来看看,线程池是什么样节约系统能源的。

第③看望直接new线程时,系统财富是怎么着变化的。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");

        for (int i = 0; i < 100; i++)
        {
            Thread t = new Thread(ThreadMethod);
            t.Start(i);
        }

        Console.ReadKey(true);
    }

    private static void ThreadMethod(object state)
    {
        Console.WriteLine("This thread's state is {0}", state);
        Thread.Sleep(2000);
    }
}

代码执行前,系统财富如下图。线程数和占用的内部存款和储蓄器见下图的深红框。

C# 2

 

代码执行后,系统财富如下图。线程数和占用的内部存款和储蓄器见下图的金棕框。

C# 3

程序运转时,内部存款和储蓄器一下充实了100多MB,线程也大增了100七个。

 

在看望利用线程池来拍卖异步请求时,系统财富是什么样转移的。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");

        for (int i = 0; i < 100; i++)
        {
            ThreadPool.QueueUserWorkItem(ThreadMethod, i);
        }

        Console.ReadKey(true);
    }

    private static void ThreadMethod(object state)
    {
        Console.WriteLine("This thread's state is {0}", state);
        Thread.Sleep(2000);
    }
}

代码执行前,系统财富如下图。线程数和占有的内部存款和储蓄器见下图的雪白框。

C# 4

 

代码执行后,系统能源如下图。线程数和占用的内部存储器见下图的森林绿框。

C# 5

程序运行时,最忙时(刚开始请求队列中线程较多时)多了13个线程,随着请求队列中线程的较少,线程池最后只维系了2,一个线程,消耗的财富最多也就10几MB。

 

经过上述的相比较,我们能够见到若是应用程序都采纳线程池来管理线程的话,确实可以减轻系统的承负,更实用的施用系统能源,保险四个应用程序能够同时运转。

要不然四个应用程序占用太多能源,其他应用程序只好等待。

当然,通过上边七个例证,我们也发觉利用线程池的程序执行时间比较长。那正是决定能源的结果,使得应用程序的异步请求逐步处理。

 

3.2 打消线程时,注册一些卓殊的操作

主线程打消子线程后,恐怕需求开展局地操作来回收财富,释放对象等等。大家得以将这几个操作注册到CancellationTokenSource的Token中,使得各类线程撤废后都会执行那一个操作。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");
        CancellationTokenSource cts = new CancellationTokenSource();
        // 注册线程取消后的操作,执行操作的顺序与注册的顺利相反
        // 比如以下2个操作,第二个操作先执行
        cts.Token.Register(() => Console.WriteLine("sub thread's object is disposed!"));  // 后执行
        cts.Token.Register(() => Console.WriteLine("sub thread's garbage is collected!"));// 先执行

        ThreadPool.QueueUserWorkItem(o => ThreadMethod(cts.Token));

        Console.WriteLine("Press any key to cancel.");
        Console.ReadKey(true);

        // 取消线程的操作
        cts.Cancel();

        Console.ReadKey(true);
    }

    private static void ThreadMethod(CancellationToken token)
    {
        do
        {
            // 线程取消前一直运行
            Console.WriteLine("Now is : {0}", DateTime.Now.ToString("HH:mm:ss"));
            Thread.Sleep(1000);
        } while (!token.IsCancellationRequested);

        Console.WriteLine("This thread is cancelled!");
    }
}

运作结果如下:(在子线程打字与印刷了5回时间后,键盘输入任意按键) 

C# 6

 

3.3 禁止撤消线程

为了防备少数线程被意外裁撤,能够经过CancellationToken.None属性来禁止有些线程被撤销。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");
        CancellationTokenSource cts = new CancellationTokenSource();

        // 线程"Thread 1"不会被取消
        ThreadPool.QueueUserWorkItem(o => ThreadMethod(CancellationToken.None, "Thread 1"));
        // 线程"Thread 2"会被取消
        ThreadPool.QueueUserWorkItem(o => ThreadMethod(cts.Token, "Thread 2"));

        Console.WriteLine("Press any key to cancel.");
        Console.ReadKey(true);

        // 取消线程的操作
        cts.Cancel();

        Console.ReadKey(true);
    }

    private static void ThreadMethod(CancellationToken token, object state)
    {
        do
        {
            // 线程取消前一直运行
            Console.WriteLine(state + " Now is : {0}", DateTime.Now.ToString("HH:mm:ss"));
            Thread.Sleep(1000);
        } while (!token.IsCancellationRequested);

        Console.WriteLine(state + " is cancelled!");
    }
}

运行结果如下:

C# 7

 

3.4 关联多少个撤废操作

能够将多少个废除操作关联起来,那样主线程能够很不难的查实是或不是产生了废除操作。

using System;
using System.Threading;

public class CLRviaCSharp_18
{
    static void Main(string[] args)
    {
        Console.WriteLine("Main Thread");
        CancellationTokenSource cts1= new CancellationTokenSource();
        CancellationTokenSource cts2= new CancellationTokenSource();

        ThreadPool.QueueUserWorkItem(o => ThreadMethod(cts1.Token, "Thread 1"));
        ThreadPool.QueueUserWorkItem(o => ThreadMethod(cts2.Token, "Thread 2"));

        // 将ctsLink与cts1和cts2关联起来
        CancellationTokenSource ctsLink = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);

        Console.WriteLine("Press any key to cancel.");
        Console.ReadKey(true);

        // 取消线程2的操作,cts2.IsCancellationRequested属性变为True
        // 同时ctsLink的IsCancellationRequested也变为True
        cts2.Cancel();

        // 通过检验ctsLink就可以知道是否有线程被取消
        if (ctsLink.IsCancellationRequested)
            Console.WriteLine("Some thread has been cancelled!");
        else
            Console.WriteLine("No thread has been cancelled!");

        Console.ReadKey(true);
    }

    private static void ThreadMethod(CancellationToken token, object state)
    {
        do
        {
            // 线程取消前一直运行
            Console.WriteLine(state + " Now is : {0}", DateTime.Now.ToString("HH:mm:ss"));
            Thread.Sleep(1000);
        } while (!token.IsCancellationRequested);

        Console.WriteLine(state + " is cancelled!");
    }
}

利用线程池能够对线程进行实用的操纵,使得线程能够更好的同盟。

相关文章