CSharp的多线程

本文记录自己学习 C# 多线程的过程与思考

C#的多线程?

  • 当 C# 程序启动时,一个线程立即开始运行。这通常称为我们程序的主线程。将在其下创建其他“子”线程的线程,以下是新建线程对象,并绑定线程执行的函数,然后启动线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    using System;
    using System.Threading;
    public class GFG
    {
    static public void Main()
    {
    Console.WriteLine("Welcome to the Main thread");
    Thread thrA = new Thread(childthread);
    Thread thrB = new Thread(childthread);
    thrA.Start();
    thrB.Start();
    }
    public static void childthread()
    {
    Console.WriteLine("Welcome to the Child thread");
    }
    }
    //Welcome to the Main thread
    //Welcome to the Child thread
    //Welcome to the Child thread

C#的前景(Foreground Thread)线程?

  • 即使主线程离开其进程,也要继续运行以完成其工作的线程,这种类型的线程称为前台线程
  • 前台线程不关心主线程是否处于活动状态,它仅在完成其分配的工作时完成。换句话说,前台线程的寿命不依赖于主线程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    using System;
    using System.Threading;
    class GFG
    {
    static void Main(string[] args)
    {
    Thread thr = new Thread(mythread);
    thr.Start();
    Console.WriteLine("Main Thread Ends!!");
    }
    static void mythread()
    {
    for (int c = 0; c < 3; c++)
    {
    Console.WriteLine("mythread is in progress!!");
    Thread.Sleep(1000);
    }
    Console.WriteLine("mythread ends!!");
    }
    }
    //Main Thread Ends!!
    //mythread is in progress!!
    //mythread is in progress!!
    //mythread is in progress!!
    //mythread ends!!

C#的后台(Background Thread)线程?

  • 当 Main 方法离开其进程时的线程,这些类型的线程称为后台线程
  • 后台线程的寿命取决于主线程的寿命。如果主线程完成其进程,则后台线程也会结束其进程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    using System;
    using System.Threading;
    class GFG
    {
    static void Main(string[] args)
    {
    Thread thr = new Thread(mythread);
    thr.Name = "Mythread";
    thr.Start();
    thr.IsBackground = true;
    thr.Join();
    Console.WriteLine("Main Thread Ends!!");
    }
    static void mythread()
    {
    Console.WriteLine("In progress thread is: {0}", Thread.CurrentThread.Name);
    Thread.Sleep(1000);
    Console.WriteLine("Completed thread is: {0}", Thread.CurrentThread.Name);
    Console.WriteLine("IsBackground?: {0}", Thread.CurrentThread.IsBackground);
    }
    }
    //In progress thread is: Mythread
    //Completed thread is: Mythread
    //IsBackground?: True
    //Main Thread Ends!!

C#线程的状态?

  • 未启动状态: 创建Thread类的实例时,它处于未启动状态,这意味着当线程处于此状态时,线程尚未开始运行。或者换句话说,不调用Start()方法
    1
    Thread thr = new Thread(); 
  • 可运行状态: 准备运行的线程将移动到可运行状态。在此状态下,线程可能实际上正在运行,或者它可能已准备好在任何时刻运行。线程调度程序负责为线程提供运行时间。或者换句话说,调用Start()方法
  • 运行状态: 正在运行的线程。换句话说,线程获取处理器
  • 不可运行状态: 不可执行的线程,因为:调用Sleep()、Wait()、Supspend方法或I/O请求
  • 死亡状态: 当线程完成其任务时,线程进入死机,终止,中止状态

C#线程状态的获取及手动控制?

  • IsAlive() 检查线程是否处于活动状态
  • Sleep()方法用于在指定的毫秒内暂时暂停线程的当前执行,以便其他线程有机会开始执行,或者可以获得执行的 CPU
  • Join()方法用于使所有调用线程等待主线程,即连接线程完成其工作
  • Abort()方法用于中止线程
  • 调用 Suspend()方法来挂起线程
  • 调用 Resume()方法来恢复挂起的线程
  • Start()方法用于将线程发送到可运行状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    using System;
    using System.Threading;
    public class MyThread
    {
    public void thread()
    {
    for (int x = 0; x < 2; x++){Console.WriteLine("My Thread"); }
    }
    }
    public class ThreadExample
    {
    public static void Main()
    {
    MyThread obj = new MyThread();
    Thread thr1 = new Thread(new ThreadStart(obj.thread));
    Console.WriteLine("ThreadState: {0}",thr1.ThreadState); // ThreadState: Unstarted
    thr1.Start();
    Console.WriteLine("ThreadState: {0}",thr1.ThreadState); // ThreadState: Running
    }
    }
    //ThreadState: Unstarted
    //ThreadState: Running
    //My Thread
    //My Thread

C#线程的暂停、阻塞、停止?

  • Thread.Sleep() 方法可以将当前线程挂起一段时间
  • Thread.Join() 方法可以阻塞当前线程一直等待另一个线程运行至结束
  • Thread.Abort() 终止线程,如果在被阻塞或处于休眠状态的线程上调用 Abort 方法,则该线程将被中断,之后将中止
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    static void Main(string[] args)
    {
    Thread thread = new Thread(OneTest);
    thread.Name = "小弟弟";
    Console.WriteLine($"{DateTime.Now}:大家在吃饭,吃完饭后要带小弟弟逛街");
    Console.WriteLine("吃完饭了");
    Console.WriteLine($"{DateTime.Now}:小弟弟开始玩游戏");
    thread.Start();
    // 化妆 5 s
    Console.WriteLine("不管他,大姐姐化妆先"); Thread.Sleep(TimeSpan.FromSeconds(5));
    Console.WriteLine($"{DateTime.Now}:化完妆,等小弟弟打完游戏");
    thread.Join();
    Console.WriteLine("打完游戏了嘛?" + (!thread.IsAlive ? "true" : "false"));
    thread.Abort()
    Console.WriteLine($"{DateTime.Now}:走,逛街去");
    Console.ReadKey();
    }

    public static void OneTest()
    {
    Console.WriteLine(Thread.CurrentThread.Name + "开始打游戏");
    for (int i = 0; i < 10; i++)
    {
    Console.WriteLine($"{DateTime.Now}:第几局:" + i);
    Thread.Sleep(TimeSpan.FromSeconds(2)); // 休眠 2 秒
    }
    Console.WriteLine(Thread.CurrentThread.Name + "打完了");
    }

C#如何多线程调用类的静态方法、非静态方法?

  • 使用类方法初始化线程即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    using System;
    using System.Threading;
    class GFG
    {
    // 非静态方法
    public void Job1(object value){Console.WriteLine("Data of Thread 1 is: {0}", value);}
    // 静态方法
    public static void Job2(object value){Console.WriteLine("Data of Thread 2 is: {0}", value);}
    }
    class Program
    {
    public static void Main()
    {
    GFG obj = new GFG();
    Thread thr1 = new Thread(obj.Job1);
    Thread thr2 = new Thread(GFG.Job2);
    thr1.Start(01);
    thr2.Start("Hello");
    }
    }
    //Data of Thread 1 is: 1
    //Data of Thread 2 is: Hello

C#如何设置线程优先级?

  • 在多线程环境中,每个线程都有自己的优先级。线程的优先级显示线程获得对 CPU 资源的访问权限的频率。每当我们在C#中创建线程时,它总是被分配了一些优先级
  • 程序员可以显式地为线程分配优先级,优先级被分为5级,从高到底依次是:Highest、AboveNormal、Normal、BelowNormal、Lowest
  • 默认情况下,线程的优先级为“正常”
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    using System;
    using System.Threading;
    class GFG
    {
    static public void Main()
    {
    Thread T1 = new Thread(work);
    T1.Name = "T1";
    Thread T2 = new Thread(work);
    T2.Name = "T2";
    Thread T3 = new Thread(work);
    T3.Name = "T3";
    // 设置线程优先级
    T2.Priority = ThreadPriority.Highest;
    T3.Priority = ThreadPriority.BelowNormal;
    T1.Start();
    T2.Start();
    T3.Start();
    }
    public static void work()
    {
    Thread.Sleep(1000);
    Console.WriteLine(Thread.CurrentThread.Name+" "+ Thread.CurrentThread.Priority);
    }
    }
    //T2 Highest
    //T1 Normal
    //T3 BelowNormal

C#的线程池 ThreadPool ?

  • Thread 功能繁多,而且对线程数量没有管控,对于线程的开辟和销毁要消耗大量的资源,每次 new 一个 THread 都要重新开辟内存。如果某个线程的创建和销毁的代价比较高,同时这个对象还可以反复使用的,就需要一个池子(容器),保存多个这样的对象,需要用的时候从池子里面获取,用完之后不用销毁,在放到池子里面。这样不但能节省内存资源,提高性能,而且还能管控线程的总数量,防止滥用
  • 以下是新建设置线程池对象的例子,可知线程池里线程的执行不影响主线程的运行,线程池可以管理多线程的执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class Program
    {
    const int cycleNum = 10;
    static void Main(string[] args)
    {
    ThreadPool.SetMinThreads(1,1);
    ThreadPool.SetMaxThreads(5, 5);
    for(int i = 1; i <= cycleNum; i++)
    {
    ThreadPool.QueueUserWorkItem(new WaitCallback(testFun),i.ToString());
    }
    Console.WriteLine("主线程执行!");
    Console.WriteLine("主线程结束!");
    Console.ReadKey();
    }
    public static void testFun(object obj)
    {
    Console.WriteLine(string.Format("{0}:第{1}个线程",DateTime.Now.ToString(),obj.ToString()));
    Thread.Sleep(1000);
    }
    }
    // 主线程执行!
    // 主线程结束!
    // {0}:第{1}个线程
    // {0}:第{1}个线程
    // ...

C#的线程池ThreadPool终止?

  • 由于线程池里线程的执行不影响主线程的运行,线程池虽然可以管理多线程的执行,但是却无法知道它什么时候终止,利用线程池的函数WaitOne监控线程池是否结束
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class Program
    {
    const int cycleNum = 10; static int cnt = 10;
    static AutoResetEvent myEvent = new AutoResetEvent(false);
    static void Main(string[] args)
    {
    ThreadPool.SetMinThreads(1,1);
    ThreadPool.SetMaxThreads(5, 5);
    for(int i = 1; i <= cycleNum; i++)
    {
    ThreadPool.QueueUserWorkItem(new WaitCallback(testFun),i.ToString());
    }
    Console.WriteLine("主线程执行!");
    Console.WriteLine("主线程结束!");
    myEvent.WaitOne();
    Console.WriteLine("线程池终止!");
    Console.ReadKey();
    }
    public static void testFun(object obj)
    { cnt -= 1;
    Console.WriteLine(string.Format("{0}:第{1}个线程",DateTime.Now.ToString(),obj.ToString()));
    Thread.Sleep(5000);
    if (cnt == 0)
    {
    myEvent.Set();
    }
    }
    }
    // 主线程执行!
    // 主线程结束!
    // {0}:第{1}个线程
    // {0}:第{1}个线程
    // ...
    // 线程池终止!

C#的thread、ThreadPool、Task的区别?

  • thread是C#基础的多线程实现方式
  • threadpool其实就是thread的集合,具有很多优势,不过在任务多的时候全局队列会存在竞争而消耗资源。thread默认为前台线程,主程序必须等线程跑完才会关闭,而threadpool相反
  • Task 的背后的实现也是使用了线程池线程,但它的性能优于 ThreadPoll, 因为它使用的不是线程池的全局队列,而是使用的本地队列,使线程之间的资源竞争减少。同时 Task 提供了丰富的 API 来管理线程、控制。但是相对前面的两种耗内存,Task 依赖于 CPU,对于多核的 CPU 性能远超前两者,单核的 CPU 三者的性能没什么差别

C#的任务Task?

  • Task的背后的实现也是使用了线程池线程,但它的性能优于ThreadPoll,因为它使用的不是线程池的全局队列,而是使用的本地队列,使线程之间的资源竞争减少。同时Task提供了丰富的API来管理线程、控制。但是相对前面的两种耗内存,Task依赖于CPU对于多核的CPU性能远超前两者,单核的CPU三者的性能没什么差别
  • 以下是两种创建Task的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void test()
{
// 方式一
var testTask = new Task(() =>
{
Console.WriteLine("task start");
});
testTask.Start();
// 方式二
var factoryTeak = Task.Factory.StartNew(() =>
{
Console.WriteLine("factory task start");
});
}

C#的任务Task取消?

  • 声明“取消”类,并在创建Task时当作参数传入,然后调用“取消”类的方法即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var tokenSource = new CancellationTokenSource();//创建取消task实例
    tokenSource.Token.Register(()=> {
    Console.WriteLine("task is to cancel");
    });
    var testTask = new Task(() =>
    {
    for (int i = 0; i < 6; i++) {
    System.Threading.Thread.Sleep(1000);
    }
    },tokenSource.Token);
    Console.WriteLine(testTask.Status);
    tokenSource.Cancel();
    Console.WriteLine(testTask.Status);
    //Created
    //task is to cancel
    //Canceled

参考:

  1. https://www.cnblogs.com/yifengjianbai/p/5499493.html
  2. https://blog.csdn.net/qq_40677590/article/details/102797838