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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM