一、多线程概念

1.进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,【是系统进行资源分配的基本单位】,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,【进程是线程的容器】。程序是指令、数据及其组织形式的描述,进程是程序的实体。  

概括:
进程是程序运行的环境。进程是线程的容器。一个进程可以包含多个线程。
进程是系统进行资源分配的基本单位,而线程是系统进行资源分配的最小单位(线程不能再分)

进程就是系统上运行的软件,线程不能独立存在。进程是程序的实体,相当于进程由程序实例化形成。

2。线程?    阻塞,卡死, 等待


线程(英语:Thread)是操作系统能够进行【运算调度的最小单位】。它被包含在进程之中,是进程中的实际运作单位。【一条线程指的是进程中一个单一顺序的控制流】,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程:在进程中,进程开辟多条道路。

单一顺序:说明一条线程不能调头,阻塞,卡死,等待
线程和线程之间是异步,并行即异步

概括:线程是操作系统资源调度的最小单位。线程不能独立运行,必须包含在进程中。进程中可以包含多个线程。多线程执行时是并行(异步),无序的。线程不可再分,进程可以再分。

串行===>顺序(排队)===>阻塞(同步)           单线程
并行===>无序===>非阻塞(异步)                 多线程

线程之间要通讯。会有两种方案:同步和异步(推荐异步)。

串行:同步  单线程进行串行

单个线程中能不能异步?可以,现在没有技术方案落地。

多线程中有N个线程,但主线程只有一个,其他的线程都称为子线程(分线程)。对于单线程程序来说,程序中只有一个线程,这个线程就是主线程。

3、主线程和分线程关系


一个程序就是一个进程,然而进程里面包含若干个线程,而每个进程里面都有一个(可以说必须要有一个)线程,这个线程就是主线程,然而主线程有一天发现自己的工作太多了,在规定的时间内完不成工作,这时候他就召唤了一个小弟(子线程)帮他,他给小弟分配了一些任务,当小弟做完了分配给他的任务后,他就把小弟赶走了!这就是主线程和子线程。

主线程主要职责负责界面渲染,运行。耗时的任务(计算工作)应该交给子线程(分线程)处理

4、多线程概念?优点及缺点?


多线程是指程序中包含多个执行流(线程),即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

概括:多线程让程序同时运行多个线程,多个线程并行执行(执行时无顺序)

优点:
可以提高CPU的利用率。大大提高了程序的运行效率,用户体现好,粘性高。
重要:不阻塞,不会卡死

缺点:
a.线程也是程序,所以线程运行需要占用计算机资源,线程越多占用资源也越多。(占内存多)
b.多线程需要协调和管理,所以需要CPU跟踪线程,消耗CPU资源。(占cpu多)
c.线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。(多线程存在资源共享问题 锁)
d.线程太多会导致控制太复杂,最终可能造成很多Bug。(管理麻烦,容易产生bug)

死锁:某个资源不能空闲。

5、线程池


.NET Framework2.0时代,出现了一个线程池ThreadPool,是一种池化思想,如果需要使用线程,就可以直接到线程池中去获取直接使用,如果使用完毕,在自动的回放到线程池去;

概括:.net 2.0出现线程池,线程池中可以存在多个线程,让线程池来自动管理(垃圾回收器GC和公共语言运行时CLR)。

好处:解决了部分Thread管理不便的问题,移除了无用的Thread API。提高线程运行性能。

重要API:QueueUserWorkItem(Callback,data)

6、Task任务


Task在.net 4.0时出现,是在线程池基础上封装而来的,提供了对线程的延续,取消,等待,超时等方面功能。

7、取消任务


CancellationTokenSource类,CancellationToken结构
CancellationTokenSource cts = new CancellationTokenSource();
cts.Cancel()、cts.IsCancellationRequested属性、cts.Token属性

8、延迟,等待?


Task.Delay(100).Wait(); // 延迟并等待,注意延迟执行也是异步的。
t.Wait() //等待,会阻塞主线程
Task.WaitAny()等待任务数组中任意一个任务完成。result任务数组中第一个完成的任务索引。
Task.WaitAll()等待所有的任务完成,没有返回值
Task.WhenAny()当任务数组中任意一个任务完成的时候,去做其他事情 。返回值Task<T>
Task.WhenAll()当任务数组中所有任务都完成的时候,才去做其他事情 。返回值Task

9、拿分线程结果?分线程向主线程传值?

t.Result属性  配合  Task<T>

二、多线程应用

多线程有三种创建方法:

1、Thread

1.1Thread重点知识

A。Thread多线程对象:


实例化:Thread t1 = new Thread(两种委托),没有参数委托;带传递参数委托
多线程管理(被开发者诟病):启动:t1.Start(向线程传递的数据),中止:t1.Abort(),挂起:t1.Suspend(),重启:t1.Resume()
多线程的状态:UnStarted未启动, Running运行中, Suspended已挂起, Aborted已中止, Stoped已停止, ....
多线程等待:t1.Join(毫秒数), Thread.Sleep(毫秒数)

线程状态参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.threading.threadstate?view=netframework-4.8

B。Thread重要属性:


IsAlive 线程状态是否是存活。true存活
IsBackground 是否是后台线程。true后台
ThreadState 线程状态,比IsAlive范围大。
CurrentThread 当前执行的线程。
Name 线程名称
Priority 线程优先级

C。Thread重要方法:


new Thread(两种委托) 构造函数   ThreadStart和ParameterizedThreadStart
t.Start()启动
t.Abord()中止
t.Suspend()挂起
t.Resume()重新开始
t.Join(毫秒数)线程终止前,阻止调用线程
Thread.Sleep(毫秒数)线程休眠,延迟

Thread线程类缺点是不好用(不好控制),如果创建多个线程时,更不好管理。ThreadPool比Thread好一些,但也好不到那去。
线程锁:相对复杂

1.2Thread解释

Thread的4个重载,带参数的有parameterized
1.创建线程(分线程)
用Thread创建线程
2.启动线程
用Start()方法启动
例如t1.Start();

注意:创建和启动线程还是在主线程中进行,但是委托的lambda方法在分线程中进行及箭头后面的内容在分线程中进行

sleep在窗体上主分线程都会阻塞
注意:Thread.sleep(500);不能写到Invoke内部,因为Invoke跨线程调用主线程东西,会造成阻塞

典型错误;跨线程调用

1.3创建线程和启动线程代码示例

using System;
using System.Threading;
using System.Windows.Forms;

namespace MYN_多线程616
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnOpenThread_Click(object sender, EventArgs e)
        {
              // 两个委托(重载知识解释):
              //ThreadStart  不带参数  public delegate void ThreadStart();
              //ParameterizedThreadStart 带参数  public delegate void ParameterizedThreadStart(object obj);
            //创建Thread线程方法1
            Thread t1 = new Thread(() => 
            {
                while (true)
                {
                    Thread.Sleep(1000);//休眠1秒
                    Console.WriteLine("aaaaa");
                }
            });
            //启动线程
            t1.Start();

            //创建Thread线程方法2
            Thread t2 = new Thread((obj) =>
            {
                while (true)
                {
                    Thread.Sleep(1000);//休眠1秒
                    Console.WriteLine("bbbbb"+obj.ToString());
                }
            });
            //启动线程 并向线程obj传递参数
            t2.Start(100);

            //创建Thread的两种方法也可以写成这样t3相当于t1,t4相当于t2
            Thread t3 = new Thread(DoWork1);
            t3.Start();
            Thread t4 = new Thread(Dowork2);
            t4.Start(200);
        }

        private void Dowork2(object obj)
        {
            while (true)
            {
                Thread.Sleep(1000);//休眠1秒
                Console.WriteLine("bbbbb"+"方法2" + obj.ToString());
            }
        }

        private void DoWork1()
        {
            while (true)
            {
                Thread.Sleep(1000);//休眠1秒
                Console.WriteLine("aaaaa"+"方法1");
            }
        }
    }
}

1.4借助斐波那契数列解释Thread线程重点属性代码示例

using System;
using System.Threading;
using System.Windows.Forms;

namespace ThreadDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        // 使用递归实现斐波那契数列
        // ‌斐波那契数列是从 0 和 1 开始,后面每个数都是前两个数之和的数列‌。‌‌
        // 前几项数字‌:0、1、1、2、3、5、8、13、21、34……。
        // 计算公式‌:第 n 项等于前两项相加,即F(n)=F(n−1)+F(n−2)
        // 递归:方法内部调用自己。
        // 方法嵌套:就是方法的调用栈。方法执行的顺序流。
        public int Fib(int n)
        {
            if (n == 1 || n == 2)
                return 1;
            else
                return Fib(n - 1) + Fib(n - 2);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //int n = Fib(50);
            //MessageBox.Show(n.ToString());

            // 1。创建线程
            Thread t = new Thread((obj) =>
            {
                // 分线程中比较耗时的代码
                // 分线程和主线程是两个线程。
                // 分线程中如何访问主线程中的资源(变量,控件等
                // 变量直接通过参数传递给分线程即可。但控件Label1是主线程中的资源(控件)
                // 错误:System.InvalidOperationException:“线程间操作无效: 从不是创建控件“label1”的线程访问它。”
                // 问题:跨线程访问控件,即:在分线程中访问主线程中的控件?默认不允许的。怎么办?借助Invoke()方法。
                int n = (int)obj;
                int result = Fib(n);
                Invoke(new Action(() =>
                {
                    label1.Text = result.ToString();
                }));
            });

            // 重要的线程属性:
            t.IsBackground = true; // 掌握。是否是后台线程。是true,否false。默认false,说明默认是前台线程。前台线程会等待所有线程结束,程序才结束 。如果是后台线程,只要主线程结束,程序就结束,所有分线程会自动销毁。
            t.Priority = ThreadPriority.Highest; // 掌握。 线程优先级  线程优先级越高,在分配资源时,越先被分配。
            t.Name = "分线程1";  // 线程名称,不建议重复,建议唯一。当没有线程名称时,线程会通过ManagedThreadId线程Id来管理线程。
            //t.ManagedThreadId = 2;  // 掌握。只读
            //t.ThreadState = ThreadState.WaitSleepJoin; // 线程状态,只读
            label5.Text = t.ThreadState.ToString();  // 掌握。
            label6.Text = t.IsAlive.ToString();  // 了解

            // 2。启动线程
            t.Start(100);
            label5.Text = t.ThreadState.ToString();
            label6.Text = t.IsAlive.ToString();


            label2.Text = t.Name;

            //label3.Text = Thread.CurrentThread.Name;
            label3.Text = "主线程的Id:" + Thread.CurrentThread.ManagedThreadId.ToString();
            label4.Text = "分线程的Id:" + t.ManagedThreadId.ToString();

            // 问:哪段代码是在分线程中执行的?ParameterizedThreadStart委托匹配的方法在分线程中执行,创建线程或启动线程的代码还是在主线程中运行。

            //通过测试发现:关闭窗体Form1后,分线程并没有执行完成,也没结束 
        }

       
    }
}

注意:默认前台线程(isBackground=false;),全部线程结束线程才结束

1.5进度条案例利用Thread代码示例

using System;
using System.Threading;
using System.Windows.Forms;

namespace ThreadDemo
{
    public partial class Form2 : Form
    {
        Thread t = null;
        public Form2()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            if (t == null)
            {
                t = new Thread(new ThreadStart(DoWork));
                t.IsBackground = true;
                t.Start();
            }
        }

        private void DoWork()
        {
            while (progressBar1.Value < 100)
            {
                Thread.Sleep(500);
                Invoke(new Action(() =>
                {
                    // 错误: System.InvalidOperationException:“在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。”
                    // 解决:主线程关闭后,分线程没有自动销毁造成的。
                    progressBar1.Value += progressBar1.Step;
                }));
            }
        }

        private void btnSuspend_Click(object sender, EventArgs e)
        {
            if (t != null)
            {
                t.Suspend();// 挂起,废弃,不建议使用。
                //t.Join(3000); // 线程中止,线程中止,等待3秒
            }
        }

        private void btnRes_Click(object sender, EventArgs e)
        {
            if (t != null)
            {
                t.Resume();  // 继续,恢复线程,废弃,不建议使用。
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (t != null)
            {
                // 你在停止线程时,线程需要持续一段时间,等待CLR 处理线程的终止。
                t.Abort(); // 停止,中断,废弃,不建议使用。
                t = null;
                progressBar1.Value = 0;
            }
        }
    }
}

2.ThreadPool

进度条案例利用ThreadPool代码示例

using System;
using System.Threading;
using System.Windows.Forms;

namespace _2.TheadPoolDemo
{
    public partial class Form1 : Form
    {
        // 自己定义一些管理某个线程的标识。
        bool pause = false;  // 暂停标识
        bool stop = false; // 停止标识
        public Form1()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            // WaitCallback  public delegate void WaitCallback(object state);
            // 把一个“任务”放到线程池中执行。开发者不用在自行管理了。
            ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));  
        }

        private void DoWork(object state)
        {
            // while循环退出后,DoWork方法()所在的线程会自动销毁。销毁会持续一段时间。
            while (progressBar1.Value < 100 && !pause && !stop)
            {
                Thread.Sleep(500);
                try
                {
                    Invoke(new Action(() =>
                    {
                        progressBar1.Value += progressBar1.Step;
                    }));
                }
                catch 
                {
                }
            }
        }

        private void btnSuspend_Click(object sender, EventArgs e)
        {
            // 暂停标识 
            pause = true; 
        }

        private void btnRes_Click(object sender, EventArgs e)
        {
            // 暂停标识 
            pause = false;
            ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            stop = true; // 停止标识
        }
    }
}

注意:线程池:ThreadPool
重点知识点:QueueUserWorkItem
ThreadPool无法手动设置成后台线程,但是可以用try  catch 捕获异常

3.Task

3.1Task理论知识

1。Task任务重点掌握?


概念:Task是微软在.Net 4.0时代推出来的,也是微软极力推荐的一种多线程的处理方式,Task看起来像一个Thread,实际上,它是在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。
a. 如何实例化(多种实例化方案)
b. 如何启动任务
c. 如何向任务中传递数据(委托的使用)
d. 任务如何管理(取消  cts.Token.IsCancellationRequested/cts.Canecl())
e. 任务等待
f. 任务返回值(Task<T>泛型)
g 分线程任务中如何捕获异常(处理分线程任务中的非法情况)

重点API及类总结:
实例化及启动:new Task().Start()/Task.Run()/Task.Factory.StartNew()
线程任务取消:CancellationTokenSource类
线程任务等待:task.Wait()/Task .WaitAny()/Task .WaitAll()/Task .WhenAny()/Task .WhenAll()/factory .ContinueWhenAny()/factory.ContinueWhenAll()
线程任务返回值:Task<T>泛型的应用

2。asnyc和await是什么?


异步编程是一种编程范式(是一种异步解决方案,但它不一定多线程)。C#中的异步编程可以通过Thread,TheadPool,Task,async/await等来实现。
多线程肯定是异步的,即多线程是异步的一种实现方案。而异步不一定是多线程,因为异步也有可能出现在单线程的程序中。

async关键字是一个方法修饰符,用它声明的方法,称为异步方法。
await关键字用来等待一个Task或async方法完成,await关键字必须配合async关键字使用,不能单独应用await关键字。
await只能在async修饰的方法(即异步方法)内使用。
await不会阻塞主线程。并且能保证多个异步代码顺序。用户体验好。

3。await能等待什么?


切记:await关键字不能等待同步代码,只能等待Task或异步方法,且异步方法必须有返回值。

4。asnync和await实现异步方法有什么好处?
a. 即可以保证不阻塞,也可以保证异步执行的顺序。
b. 以同步代码的方式编写异步代码,让语法更加优美。
 

3.2Task代码示例

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _3.TaskDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // A。Task的定义方式推荐:1。Task.Run()  2。new Task()  3。TaskFactory
        //button1 创建线程
        private void button1_Click(object sender, EventArgs e)
        {
            // Run()创建并运行一个线程
            Task t = Task.Run(() =>
             {
                 while (true)
                 {
                     Console.WriteLine("AAAA");
                 }
             });

            // 2。new Task(),创建一个线程,但是没有启动,需要调用Start()启动
            Task t2 = new Task(() =>
            {
                while (true)
                {
                    Console.WriteLine("BBBB");
                }
            });
            t2.Start();

            // 3。TaskFactory 线程工厂创建线程
            TaskFactory tf = new TaskFactory();
            tf.StartNew(() =>
            {
                while (true)
                {
                    Console.WriteLine("CCCC");
                }
            });


        }
       //button2向线程传参
        private void button2_Click(object sender, EventArgs e)
        {
            //Task.Run()没有看到可以向线程内部传递参数的重载,向线程中传递能数,通过new Task()。
            Task t = new Task((obj) =>
            {
                while (true)
                {
                    Console.WriteLine(obj);
                }
            }, 100);
            t.Start();

        }
        //button3开启线程
        int i = 0;
        // 创建一个取消标识对象,它可以用来管理线程的取消。Token属性用来产生一个取消标识。Cancel()方法,取消任务。
        CancellationTokenSource cts = null;
        // 要取消任务,必须使用CancellationToken
        // 谁会产生一个Token取消标识呢?CancellationTokenSource对象
        //每一个线程的CancellationTokenSource对象都是唯一的
        private void button3_Click(object sender, EventArgs e)
        {
            cts = new CancellationTokenSource();
            Task.Run(() =>
            {
                // cts.IsCancellationRequested = false没有取消,true取消。cts.Token.IsCancellationRequested也行。
                while (!cts.Token.IsCancellationRequested)
                {
                    Console.WriteLine("AAAA" + i);
                    i++;
                }
            }, cts.Token);

            //cts = new CancellationTokenSource();
            //Task t = new Task(() => { }, cts.Token);
        }
   //button4取消线程
        private void button4_Click(object sender, EventArgs e)
        {
            // IsCancellationRequested是只读属性,不能赋值,只能靠Cancel()方法来修改。
            //cts.IsCancellationRequested = true;
            //cts.Token.IsCancellationRequested = true;
            cts.Cancel();  // 取消任务,这个方法会造成cts.IsCancellationRequested的变化成true。
        }
     //button5拿线程结果
        private async void button5_Click(object sender, EventArgs e)
        {
            // 把Task.Run()创建的线程保存到变量t上,为了拿t中返回的结果。
            Task<string> t = Task.Run(() =>
            {
                Console.WriteLine("线程中的其他业务逻辑!");
                Thread.Sleep(10000);
                return "hello world";
            });


            //var result =  t.Result;  // 会阻塞,能保证代码顺序。
            var result2 = await t;  // await等待,等待的谁? t任务,等待t正常完成。等待是t完成后的结果。

            // await 好处:1.线程不会阻塞,2.保证代码异步执行的代码顺序。
            // await 要求:只能配合async关键字一起使用,不能单独使用。

            Console.WriteLine(result2);
            Console.WriteLine("aaaa");

            /*Method1();
            Method2();
            Method3();  // (可等待),异步方法,且有返回值
            Method4();  // (可等待),异步方法,且有返回值
            Method5();  // (可等待),异步方法,且有返回值*/
        }


        HttpClient httpClient = new HttpClient();
        // 普通方法
        public void Method1() { }
        // 异步方法:看方法返回值类型,返回值类型前有async关键字,就是异步方法。
        // 异步方法,async一般配合void,task,task<T>使用
        // void 给控件绑定事件时,才配合void使用, 有一定局限性。
        // task, task<T>
        public async void Method2()
        {

        }
        public async Task Method3() { }

        public async Task<string> Method4()
        {
            return "hello world";
        }
        // 多线程肯定是异步的,异步不是一定是多线程。
        public Task Method5()
        {
            return new Task(() =>
            {

            });
        }
          //button6WhenAny和WhenAll
        private void button6_Click(object sender, EventArgs e)
        {
            Task t1 = Task.Run(() =>
            {
                Console.WriteLine("线程1");
            });
            Task t2 = Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("线程2");
            });
            // 当t1,t2任务完成任意一个时,继续执行线程3
            //Task.WhenAny(t1, t2).ContinueWith((t) =>
            //{
            //    Console.WriteLine("线程3");
            //});

            // 当t1,t2任务全部完成时,继续执行线程3
            Task.WhenAll(t1, t2).ContinueWith((t) =>
            {
                Console.WriteLine("线程3");
            });
        }
          //button7线程等待
        private async void button7_Click(object sender, EventArgs e)
        {
            Task t = Task.Run(async () =>
            {
                Console.WriteLine("线程1");
                // Thread.Sleep(10000);  // 休眠,延迟10秒。
                await Task.Delay(10000); // 不会阻塞线程。
            });

            //t.Wait();  // 等待t任务完成,好处:保证顺序。缺点:线程阻塞。
            await t;
            Console.WriteLine("aaaa");

            

        }
    }
}

注意:异步方法一般配合void、 task、 task<T>
void给控件绑定事件时使用
异步方法:看方法返回值类型,返回值类型前面有async的便是异步方法

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐