天天看點

【.NET 6】多線程的幾種打開方式和代碼示範

前言:

多線程無處不在,平常的開發過程中,應該算是最常用的基礎技術之一了。以下通過Thread、ThreadPool、再到Task、Parallel、線程鎖、線程取消等方面,一步步進行示範多線程的一些基礎操作。歡迎大家圍觀。如果大佬們有其他關于多線程的拓展,也歡迎在評論區進行留言,大佬們的知識互助,是.net生态發展的重要一環,歡迎大佬們進行留言,幫助更多的人。

本文章為了防爬蟲,特此放上原文連結,如果大家在其他地方(部落格園與CSDN以外的地方)搜尋到,可以點以下連結,跳轉回原文:

https://www.cnblogs.com/weskynet/p/16391095.html

以下部落格内容使用的一些環境:

系統環境:WIN 10

.NET 環境: .NET 6

VS 環境:VS 2022

其他:沒了

以下正文:

1、先建立一個.NET 6控制台項目,用來當做該部落格文章的實驗使用。

【.NET 6】多線程的幾種打開方式和代碼示範

2、快速建立一個線程。ParameterizedThreadStart是一個委托,傳入的參數是一個object類型。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

ParameterizedThreadStart threadStart = new((obj) => {
    Console.WriteLine($"目前線程 的 ID = {Thread.CurrentThread.ManagedThreadId}");
});

Thread thread = new Thread(threadStart);
thread.Start();
Console.WriteLine($"線程ID  = {thread.ManagedThreadId}");
Console.ReadLine();      

3、以上代碼執行結果下圖所示

【.NET 6】多線程的幾種打開方式和代碼示範

4、建立一個類TestThread,以及一個測試方法,用來做測試使用。

【.NET 6】多線程的幾種打開方式和代碼示範

5、在program裡面,把輸出改成調用上面的方法再進行測試一下。

【.NET 6】多線程的幾種打開方式和代碼示範

6、執行以後的輸出結果,如下圖所示

【.NET 6】多線程的幾種打開方式和代碼示範

7、線程的等待(睡眠)。最簡單的方式,是直接 Thread.Sleep(毫秒);

【.NET 6】多線程的幾種打開方式和代碼示範

8、Thread的Join方法。代表線程執行完畢以後,才可以繼續執行後續的代碼。如下圖所示,在thread線程内部執行完成以後,很快就接着執行最後的列印輸出方法了。可以和以上的第7點進行比較輸出結果。

【.NET 6】多線程的幾種打開方式和代碼示範

9、Thread的Join方法,還可以傳入參數,參數是毫秒值。代表等下目前線程執行多長時間,如果超出設定的毫秒數,就不等了,直接執行後續的代碼。

【.NET 6】多線程的幾種打開方式和代碼示範

10、新增一個Test2方法,用來測試線程池ThreadPool使用。

【.NET 6】多線程的幾種打開方式和代碼示範

11、WaitCallback也是一個委托。傳入需要線上程池内執行的方法名稱。以下代碼内,“線程池”字元串為執行的方法對應的參數。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

using MultiThread;

Console.WriteLine("Hello, World!");


ThreadPool.QueueUserWorkItem(new WaitCallback(TestThread.Test2),"線程池");

Console.ReadLine();      

12、除了直接傳入回調方法,也可以直接線上程池開啟的方法内,直接寫代碼塊來當做多線程執行的部分。如下圖所示,睡眠1000ms以及執行的方法,線上程池内運作。

【.NET 6】多線程的幾種打開方式和代碼示範

13、線程池内,可以通過設定Manual信号量,來識别線程池内的線程時候執行完成。一般用 .Set(); 和 .WaitOne(); 結對進行,如下圖代碼、注釋部分以及執行結果。(可以對比輸出時間)

【.NET 6】多線程的幾種打開方式和代碼示範

14、使用Task快讀建立一個線程。如下圖所示。最簡單的方法:Task.Run(()=>{ 代碼塊; });  

【.NET 6】多線程的幾種打開方式和代碼示範

15、也可以用以下方式,手動進行start啟動,如圖的代碼所示。

【.NET 6】多線程的幾種打開方式和代碼示範

16、也可以使用Task.Factory建立一個任務工廠來實作。

【.NET 6】多線程的幾種打開方式和代碼示範

17、如果需要等待子線程執行完畢,才執行後續操作,可以使用Wait(); 來實作。

【.NET 6】多線程的幾種打開方式和代碼示範

18、如果隻想等待子線程執行指定的時間,可以通過使用 Wait(毫秒數); 來實作。這樣等待,例如500ms以後,不管子線程是不是還在浪,都不會等待,直接繼續執行後續代碼。

【.NET 6】多線程的幾種打開方式和代碼示範

19、 如果要在等待一段時間以後執行某些當做,可以使用Task.Delay(時間毫秒數).ContinuwWith( 要執行的代碼塊);如下圖所示的代碼、注釋以及運作輸出結果。

【.NET 6】多線程的幾種打開方式和代碼示範

20、如果有多個任務在執行期間,在任意一個線程執行完畢以後進行執行某種操作,可以使用 ContinueWhenAny來進行。如下圖所示的代碼、注釋和運作結果,以及圖後附有源碼。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");

Task[] tasks = new Task[3];
TaskFactory factory = new();
tasks[0] = factory.StartNew(x => {
    Thread.Sleep(1000);
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> tasks 0");
},null);

tasks[1] = factory.StartNew(x => {
    Thread.Sleep(2000);
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> tasks 1");
}, null);

tasks[2] = factory.StartNew(x => {
    Thread.Sleep(3000);
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> tasks 2");
}, null);

factory.ContinueWhenAny(tasks, x =>
{
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不曉得要列印啥子 ~ ~ ");

});

Console.ReadLine();      

21、如果要等任務全部執行完畢以後才執行某個代碼塊,可以使用ContinueWhenAll。

【.NET 6】多線程的幾種打開方式和代碼示範

22、使用TaskWaitAny() 也可以實作任意任務執行完畢以後,執行後續動作。但是會占用主線程資源。如圖所示代碼,大佬們應該可以看出來為什麼了。

【.NET 6】多線程的幾種打開方式和代碼示範

23、同樣的,Task也可以在等待全部任務執行完畢以後進行執行後續動作。如下圖示範。

【.NET 6】多線程的幾種打開方式和代碼示範

24、Parallel允許線程并行執行。同時最大線程執行數量,類似于ThreadPool可以設定最大并發數量類似。其他不多說,看以下的代碼和示範效果。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

using MultiThread;

Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");

ParallelOptions parallelOptions = new();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.Invoke(parallelOptions,
    () =>
    {
        Thread.Sleep(1000);
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>  para1");
    },
    () =>
    {
        Thread.Sleep(2000);
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para2");
    },
    () =>
    {
        Thread.Sleep(3000);
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para3");
    });
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不曉得要列印啥子 ~ ~ ");

Console.ReadLine();      

25、Parallel也可以周遊執行。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

using MultiThread;

Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");

ParallelOptions parallelOptions = new();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10,parallelOptions, s =>
{
    Thread.Sleep(100);
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>  para{s}");
});

Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不曉得要列印啥子 ~ ~ ");

Console.ReadLine();      

26、新增一個方法,用來測試多線程鎖使用。

【.NET 6】多線程的幾種打開方式和代碼示範

27、在不加鎖的情況下執行執行以下代碼,方法體幾乎同時被執行。但是實際上方法體如果隻允許被同時一個線程通路的話,那麼這樣搞肯定是會亂子的,是以需要鎖。

【.NET 6】多線程的幾種打開方式和代碼示範

28、加了鎖以後,檢視到執行的結果,時間間隔基本上是1s左右,說明該方法體确實一次隻被一個線程調用了。

【.NET 6】多線程的幾種打開方式和代碼示範

29、另一種鎖(原子鎖),可以定義一個變量來進行原子交換。它的使用場景,一般是在輪詢進行處理某些業務的時候,并且同時隻允許一個線程進來,就可以使用這種鎖。

和lock鎖差別:lock鎖是代碼還沒執行完,線程會一直等待,等執行完了就會繼續進來。如果線程一直被建立,lock外邊會堆積越來越多的線程和資源,最嚴重的情況會導緻系統記憶體不斷飙升直到爆滿;原子鎖的作用是,用于驗證代碼塊是不是執行完了,還沒執行完,就不鳥他了,線程也不會等待下去,而是直接跳過這部分的代碼,繼續執行後續的操作。如果後續沒事情做了,那該幹嘛幹嘛了。

【.NET 6】多線程的幾種打開方式和代碼示範

30、原子鎖執行效果如下,一部分線程判斷到代碼被鎖住,就跳過不管了,是以就不會有輸出。

【.NET 6】多線程的幾種打開方式和代碼示範

31、測試線程取消。先開啟一些線程,以及有關的操作,如下圖所示。

【.NET 6】多線程的幾種打開方式和代碼示範

32、然後執行。結果比較尴尬,顯示都是第100号線程,這是因為Task是多線程,在建立過程中,可能已經讓i都執行到頭了,是以再次擷取到的i都是最後的值,即100.

【.NET 6】多線程的幾種打開方式和代碼示範

33、在建立任務之前,引入一個中間變量,用來代替被周遊的i。然後執行結果和其他代碼說明,如圖所示。

【.NET 6】多線程的幾種打開方式和代碼示範

34、看不到異常資訊,那改成Task直接走一波,然後通過Task.WaitAll();進行捕捉異常資訊。如代碼注釋和示範截圖所示。

【.NET 6】多線程的幾種打開方式和代碼示範

代碼:

Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");
try
{
    Task[] tasks = new Task[100];
    CancellationTokenSource cancellation = new CancellationTokenSource();
    for (int i = 0; i < 100; i++)
    {
        string str = i.ToString();
        tasks[i]= Task.Run(() =>
        {
            Thread.Sleep(100);
            try
            {
                if (str == "10")
                {
                    throw new Exception($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}  >>> 第 -{str}- 号線程開始放棄治療~~  線程ID  = {Thread.CurrentThread.ManagedThreadId}");
                }
            }
            catch (Exception ex)
            {
                cancellation.Cancel(); // 捕獲異常,線程後續所有的線程都取消操作
                Console.WriteLine(ex.Message);
            }
            cancellation.Token.ThrowIfCancellationRequested();
            if (cancellation.IsCancellationRequested == false)  // 預設為false,代表正常
            {
                Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}  >>> 第 -{str}- 号線程執行正常~~  線程ID  = {Thread.CurrentThread.ManagedThreadId}");
            }
            else
            {
                Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")}  >>> 第 -{str}- 号線程執行異常~~  線程ID  = {Thread.CurrentThread.ManagedThreadId}");
            }
        }, cancellation.Token);
    }
    Task.WaitAll(tasks);
    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不曉得要列印啥子 ~ ~ ");

}
catch (AggregateException ae)
{
    foreach (var ex in ae.InnerExceptions)
    {
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> {ex.Message}");
    }
}

Console.ReadLine();      

35、以上就是該偏文章的全部内容。如果對你有幫助,歡迎點贊、轉發、或留言。如需轉發,請注明出處:https://www.cnblogs.com/weskynet/p/16391095.html

如果想和我一起吹牛談人生聊技術,或者和其他小夥伴一起吹牛談人生聊技術,也可以掃以下的二維碼加我微信好友,我不會介意的:

或者也可以在該文章的原文【https://www.cnblogs.com/weskynet/p/16391095.html】裡面,點選最下方的QQ群組連結,加入QQ群,我也不介意。

沒了,最後祝大家撸碼愉快~~

歡迎加入QQ群:

群号:1079830632

【.NET 6】多線程的幾種打開方式和代碼示範

繼續閱讀