C#多线程
前期知识:
1.进程和线程是啥?
进程:进程就是一个应用程序,对电脑的各种资源的占用
线程:线程是程序执行的最小单位,任何操作都是线程完成的,线程依托进程存在的,一个进程可以有多个线程
2.多线程
为啥会出现多此线程?
计算机的角度,因为CPU太快了,其他硬件跟不上CPU的速度。CPU可以分为时间片,大概就是时间分片---上下文切换(加载环境--计算--保存环境)。
从微观角度上说,一个核一个时刻,只能执行一个线程;宏观上来说是多线程并发。
另外CPU多核,可以独立工作。例如计算机是4核8线程中,核指的就是物理的核,线程指的是物理的核。
3.C#语言的线程
就是指Thread(.net 1.0的时候就出现了),Thread是一个类,是C#语言多线程对象的封装。
4.同步和异步
这个是指方法的描述。
同步:当前行,当前方法执行完成后,再开始下一行,阻塞式的。
异步:不会等待方法的完成,会直接进入下一行,非阻塞式的。
异步多线程方法发块,因为每个线程并发运算;但是并不是线性增长。同步方法,时间长,资源占用少;异步方法时间快,CPU资源占用多,异步是资源换时间。多线程可能资源不够,还有管理成本。
5.C#中的异步和多线程的区别?
多线程就是多个Thread并发;
异步是硬件式的异步;
6.应用场景
本身异步多线程就是顺序不可控的,当要控制顺序的时候,需要用到异步回调,3种方式可以实现
注意:不要忘记本心,应用多线程的前提:多个独立的任务可以同时运行。
7.委托的异步调用
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace _303Thread { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //不带返回值的委托异步调用 { Action action; action = DoSomething; //action.Invoke();//同步 IAsyncResult iAsyncResult = null; AsyncCallback async = new AsyncCallback(ia => { Console.WriteLine(object.ReferenceEquals(ia, iAsyncResult)); Console.WriteLine("所有任务都完成了"); }); iAsyncResult = action.BeginInvoke(async, "button1_Click"); //上面一行代码的含义:action.BeginInvoke开启一个异步线程, //异步线程的执行完,产生一个IAsyncResult //最后执行回调委托,async。 } //带返回值的委托异步调用 { Func<int> func = () => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Thread.Sleep(100); return DateTime.Now.Day; }; //int result = func.Invoke(); IAsyncResult asyncResult = func.BeginInvoke(ia => { Console.WriteLine(ia.AsyncState); Console.WriteLine(func.EndInvoke(ia)); }, "传入的参数"); //Console.WriteLine($"{func.EndInvoke(asyncResult)}"); } } private void DoSomething() { for (int i = 0; i < 5; i++) { Console.WriteLine($"*********DoSomething Start:{Thread.CurrentThread.ManagedThreadId}***********"); for (int j = 0; j < 100; j++) { Thread.Sleep(5); } Console.WriteLine($"*********DoSomething End:{Thread.CurrentThread.ManagedThreadId}***********"); } } } }
Task专辑
.net framwork 3.0出现的。命名空间是System.Thread.Tasks。
Task是基于ThreadPool封装的,之后的多线程主流都使用Task。
什么时候用多线程?
首先,任务得能够并发运行。任务可以分拆,且各不相干。
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading; 7 using System.Threading.Tasks; 8 9 namespace _00_测试的例子 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 List<Task> list = new List<Task>(); 16 Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 17 list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client"))); 18 list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal"))); 19 list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service"))); 20 21 //Task的一个API的含义 22 //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面 23 //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms 24 //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面 25 26 Task.WhenAny(list.ToArray()).ContinueWith(t => 27 { 28 Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 29 });//等待某个任务完成后,不卡界面,非阻塞 30 Task.WhenAll(list.ToArray()).ContinueWith(t => 31 { 32 Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 33 });//等待所有任务完成后,不卡界面,非阻塞 34 35 //Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}"); 36 Console.ReadKey(); 37 } 38 /// <summary> 39 /// 做事情 40 /// </summary> 41 /// <param name="str1"></param> 42 /// <param name="str2"></param> 43 public static void DoSomething(string str1, string str2) 44 { 45 Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }"); 46 long result = 0; 47 for (int i = 0; i < 100000000; i++) 48 { 49 result += i; 50 } 51 Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }"); 52 } 53 54 } 55 }
Task的一些API
多线程中,Task的4个API熟练使用,就能应付工作中大部分多线程了。
Task.WaitAny:阻塞当前线程,等待某个任务完成,进入下一行,会卡界面
Task.WaitAll:阻塞当前线程,等待所有任务完成,进入下一行,会卡界面
Task.WhenAny:等待某个任务完成后,不卡界面,非阻塞
Task.WhenAll:等待所有任务完成后,不卡界面,非阻塞
taskFactory.ContinueWhenAny和taskFactory.ContinueWhenAll和上面Task的API功能相同。
Task.WhenAny(list.ToArray()).ContinueWith()和Task.WhenAll(list.ToArray()).ContinueWith()和上面的taskFactory的ContinueWhenAny()和ContinueWhenAll()功能是一样的。
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading; 9 using System.Threading.Tasks; 10 using System.Windows.Forms; 11 12 namespace _00_测试的例子 13 { 14 public partial class Form1 : Form 15 { 16 public Form1() 17 { 18 InitializeComponent(); 19 } 20 21 private void button1_Click(object sender, EventArgs e) 22 { 23 List<Task> list = new List<Task>(); 24 Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 25 list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client"))); 26 list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal"))); 27 list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service"))); 28 29 //Task的一个API的含义 30 //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面 31 //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面 32 //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms 33 34 Task.WhenAny(list.ToArray()).ContinueWith(t => 35 { 36 Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 37 });//等待某个任务完成后,不卡界面,非阻塞 38 Task.WhenAll(list.ToArray()).ContinueWith(t => 39 { 40 Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 41 });//等待所有任务完成后,不卡界面,非阻塞 42 43 {//TaskFactory的两个API函数,跟上面Task的函数功能相同 44 //TaskFactory taskFactory = new TaskFactory(); 45 //taskFactory.ContinueWhenAny(list.ToArray(), t => 46 //{ 47 // Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 48 //}); 49 50 //taskFactory.ContinueWhenAll(list.ToArray(), tList => 51 //{ 52 // Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}"); 53 //}); 54 } 55 56 Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}"); 57 } 58 /// <summary> 59 /// 做事情 60 /// </summary> 61 /// <param name="str1"></param> 62 /// <param name="str2"></param> 63 public static void DoSomething(string str1, string str2) 64 { 65 Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }"); 66 long result = 0; 67 for (int i = 0; i < 1000000000; i++) 68 { 69 result += i; 70 } 71 Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }"); 72 } 73 } 74 }
await和asycn,.net framwork4.5出现的语法糖
1>通常是成对出现的,async是放到方法上的,任意个方法都可以使用。await是放到Task前面的。
2>await和asycn要么不用,要么用到底。用到最后一层,一层层传到上层。
3>任何一个方法前面都可以加上async,方法里面await可有,也可没有。await一定是出现在task之前
之所以用,目的是想用同步的方式,写异步操作。
下面一段代码,其中方法Show()是实现方式就是ShowNew()。await后面的那份部分代码,其实就是task.ContinueWith回调函数。
注意:非UI线程中,await后面的动作都是子线程完成的;UI线程中,await后面的动作都是主线程完成的。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace _00_测试的例子 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 Console.WriteLine($"Main1:{Thread.CurrentThread.ManagedThreadId}"); 15 Task task = Show(); 16 Console.WriteLine($"Main2:{Thread.CurrentThread.ManagedThreadId}"); 17 Console.WriteLine("====================================================="); 18 Console.WriteLine($"Main1==:{Thread.CurrentThread.ManagedThreadId}"); 19 Task task2 = ShowNew(); 20 Console.WriteLine($"Main2==:{Thread.CurrentThread.ManagedThreadId}"); 21 Console.ReadKey(); 22 } 23 public async static Task Show() 24 { 25 Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}"); 26 Task task = Task.Factory.StartNew(() => 27 { 28 29 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}"); 30 Thread.Sleep(1000); 31 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}"); 32 }); 33 await task; 34 Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}"); 35 36 Task task1 = Task.Factory.StartNew(() => 37 { 38 Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}"); 39 Thread.Sleep(2000); 40 Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}"); 41 }); 42 await task1; 43 Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}"); 44 } 45 public async static Task ShowNew() 46 { 47 Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}"); 48 Task task = Task.Factory.StartNew(() => 49 { 50 51 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}"); 52 Thread.Sleep(1000); 53 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}"); 54 }); 55 56 Task task4 = task.ContinueWith((t) => 57 { 58 Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}"); 59 60 Task task1 = Task.Factory.StartNew(() => 61 { 62 Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}"); 63 Thread.Sleep(2000); 64 Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}"); 65 }); 66 task1.ContinueWith(t1 => 67 { 68 Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}"); 69 }); 70 }); 71 } 72 } 73 }
异常处理,线程取消,多线程临时变量
1>异常处理
子线程里面不允许出现异常,自己处理好异常
2>线程取消
使用场景,多线程并发,某个失败后,通知其他线程,都停了吧别做了。
Task是外部无法终止的,因为线程是OS的资源,无法掌控什么时候取消。线程自身停止自己,访问一个公共变量,修改它,线程不断访问它。
CancellationTokenSource去标志是否取消任务,Cancel取消,IsCancellationRequested是否已经取消。
Token启动Task的时候传入,如果Cancel了,这个任务放弃启动,抛出一个异常。
3>多线程临时变量
下图中,i为什么等于5,k反而是0到4?
答案:i最后执行了++,就变成了5。全程只有一个i值。k的话,for循环5次,就有5个k。
异步和多线程的区别?
异步:是对方法的描述,是一个目的或者说是目标。异步不会等待方法完成,会直接进入下一行,非阻塞。
多线程:是多个Thread并发。是一种实现异步的方式。
线程安全
公共变量:都能访问的局部变量
lock只能锁引用类型,占用这个引用链接
安全队列ConcurrentQueue一个线程完成操作
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading; 7 using System.Threading.Tasks; 8 9 namespace _00_测试的例子 10 { 11 class Program 12 { 13 static int iTotalCount = 0;//全局变量 14 static List<int> list = new List<int>(); 15 private static readonly object oValue = new object(); 16 static void Main(string[] args) 17 { 18 Start0(); 19 Console.ReadKey(); 20 } 21 public static void Start0() 22 { 23 TaskFactory taskFactory = new TaskFactory(); 24 List<Task> listTask = new List<Task>(); 25 int iTotal = 0; 26 for (int i = 0; i < 10000; i++) 27 { 28 int iValue = i; 29 listTask.Add(taskFactory.StartNew(() => 30 { 31 //lock(oValue) 32 //{ 33 iTotalCount += 1; 34 iTotal += 1; 35 list.Add(iValue); 36 //} 37 })); 38 } 39 Task.WaitAll(listTask.ToArray()); 40 Console.WriteLine($"{iTotalCount}"); 41 Console.WriteLine($"{iTotal}"); 42 Console.WriteLine($"{list.Count}"); 43 Console.ReadKey(); 44 } 45 } 46 }
上段代码执行结果如下图:循环10000次,实际执行结果却不是10000.因为多个线程同时访问变量,造成了数据丢失
开放上段代码中屏蔽的部分,如下图,
iTotalCount 和iTotal 和list个数都是10000万次了。这个是Lock锁住了这三个变量,让同一时间,是有一个线程访问。Lock只能锁引用类型,锁的是引用链接。