前期知識:
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只能鎖引用類型,鎖的是引用鏈接。