zoukankan      html  css  js  c++  java
  • 线程与线程池以及任务和协商式取消

    一、线程

    使用System.Threading命名空间下的Thread类即可创建专有线程

    var t = new Thread(() => Console.WriteLine("new thread"));

    构造函数有如下四个版本

    Thread(ThreadStart start);

    public Thread(ThreadStart start, int maxStackSize);

    public Thread(ParameterizedThreadStart start);

    public Thread(ParameterizedThreadStart start, int maxStackSize)

    其中ThreadStart、ParameterizedThreadStart 是两个委托类型,表示将要执行的函数,一个能接受参数,一个不接受参数。原型如下:

    public delegate void ParameterizedThreadStart(object obj);

    public delegate void ThreadStart();

    使用有参版本如下:

    var t = new Thread(state => Console.WriteLine($"My name is {state}"));
    t.Start("HK");

    调用Start如下的重载方法即可,内部会使用这个参数并调用相应的委托。

    public void Start(object parameter)

    一般来说,你将要执行的函数一般会将这个参数进行强制转换,以便你使用。如下所示


    var t = new Thread(state =>
    {
         Point p = (state as Point) ?? throw new ArgumentException("无效的参数类型");//强制转型
         Console.WriteLine($"The sum is {p.X + p.Y}");
    });

    t.Start(new Point(1,2));

    用到的类如下:

    public class Point
    {
         public int X { get; private set; }
         public int Y { get; private set; }
         public Point(int x, int y)
         {
             this.X = x;
             this.Y = y;
         }
    }

    函数体执行完之后,该线程就会被释放,但是该创建该线程的实例对象的内存不会释放,直到GC回收该内存。

    使用该类创建线程有明显的一个缺点,不能使用有返回值的委托。并且线程不能重用,一经创建,执行完函数体之后,便被销毁了,由于创建线程是有资源消耗的,对于客户端程序可能不明显,但对于高性能的服务端程序,频繁的创建线程会浪费许多系统资源。所以要用下面要介绍的线程池。



    二、线程池

    顾名思义,就是存储线程的一个池子,当有任务进来的时候,线程池分配一个线程去执行该任务,当该任务执行完之后,线程不会不销毁,而是由线程池回收,待有新任务到来时,再去分配线程执行新任务。线程池中的线程只有第一次创建时才会损耗性能,创建完成之后,便不会了。

    同样是在System.Threading命名空间下,使用ThreadPool静态类如下的静态方法即可

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

    public static bool QueueUserWorkItem(WaitCallback callBack);

    其中WaitCallback 也是一个委托类型,与ParameterizedThreadStart委托类型是一样的

    public delegate void WaitCallback(object state);

    public delegate void ParameterizedThreadStart(object obj);


    注意与Thread类不同,使用QueueUserWorkItem方法之后,该任务会自动加入待执行列表,而不用像Thread类一样显示调用Start方法。注意不管是QueueUserWorkItem还是Start,执行之后函数体(任务)并不一定会马上执行,而是等待调度,得到执行权之后才会执行。在系统资源不紧张的情况下,通常会立即执行。注意使用线程池创建的线程默认是后台线程,使用Thread类创建的实例,默认是前台线程,在Thread类的实例对象下,可以设置IsBackground属性为false从而把线程设置为后台线程。还可以通过Thread类的静态对象Thread.CurrentThread来获得当前线程实例对象,从而设置线程的前后台属性。前台线程和后台线程的区别见第三点。


    使用如下:

    ThreadPool.QueueUserWorkItem((state) =>
    {
         Console.WriteLine("new Task");
    });

    这会造成工作项进入一个待执行队列,然后线程池会分配线程去执行这个工作项,线程池刚开始是空的,一但有工作项进来,就会创建线程去执行这个工作项,工作项完成之后,线程会由线程池回收复用,继续去执行队列中的工作项。当线程池中空闲的线程为0时,且待执行队列还有工作项的时候,线程池会创建新的线程(如果系统资源足够的话,否则只能等待有空闲的线程去执行队列中的工作项)去执行这个工作项。

    然而线程返回值还是不能直接得到,要想直接得到返回值,可以通过System.Threading.Tasks命名空间下的Task<T>类。

    三、前台线程和后台线程以及协商式取消

    在CLR中,线程要么是前台线程,要么是后台线程,每个应用程序至少有一个前台线程,所有前台线程停止运行后,CLR将强制终止仍在运行的后台线程。

    如下所示:

    static void Main(string[] args)
    {
         var t = new Thread(() =>
         {
             Thread.Sleep(1000);
             Console.WriteLine("Background Thread");
         });
         t.IsBackground = true;//设置为后台线程
         t.Start();
         Console.WriteLine("Main thread over");
    }

    image

    设置t.IsBackground = false

    image

    有时我们想让执行的工作取消,可以使用协商式取消的方式,为什么叫协商式,是因为要双方都要遵守一个约定。我们使用System.Threading命名空间下的CancellationTokenSource类。然后我们可以通过该类实例下的Token属性获得一个值类型实例,这个值类型是CancellationToken,同样是在System.Threading命名空间下。双方使用这个实例便可实现协作式取消操作。如下所示:

    private static void OP(int n,CancellationToken token)
    {
         Thread.CurrentThread.IsBackground = false;
         int sum = 0;
         for (int i = 0; i < n; i++)
         {
             if(token.IsCancellationRequested)//如果是通过Task执行的,这里应该抛出一个异常(token.ThrowIfCancellationRequested),因为任务可以有返回值,如果像这里一样处理,则无法知道该任务是正常结束了还是取消了。
             {                                               //另外注意,通过Thread类的实例或者是使用ThreadPool创建的线程是无法在调用线程直接捕获异常然后进行处理的,所以这里不可以选择抛出异常,一旦抛出异常则应用程序就会终止。
                 Console.WriteLine("work is not over");
                 Console.WriteLine(sum.ToString());
                 return;
             }
             sum += i;
             Thread.Sleep(100);
         }
         Console.WriteLine("work is over");
         Console.WriteLine(sum.ToString());
    }
    static void Main(string[] args)
    {
         var cts = new CancellationTokenSource();
         ThreadPool.QueueUserWorkItem((statue) => OP(10,cts.Token));
         Console.ReadLine();
         cts.Cancel();
    }

    提前按下回车键

    image

    正常完成

    image

  • 相关阅读:
    Spring+redis整合遇到的问题集以及注意事项
    Map源码学习之HashMap
    评分---五星好评
    下拉复选框
    倒计时按钮—获取手机验证码按钮
    input上传文件个数控制
    ajax请求完之前的loading加载
    获取浏览器滚动距离
    获取浏览器可视区域宽高
    获取元素尺寸宽高
  • 原文地址:https://www.cnblogs.com/hkfyf/p/12093878.html
Copyright © 2011-2022 走看看