小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

C# 多線程(菜鳥教程及愛整理)

 吳敬銳 2022-12-09 發(fā)布于廣東

線程 被定義為程序的執(zhí)行路徑。每個(gè)線程都定義了一個(gè)獨(dú)特的控制流。如果您的應(yīng)用程序涉及到復(fù)雜的和耗時(shí)的操作,那么設(shè)置不同的線程執(zhí)行路徑往往是有益的,每個(gè)線程執(zhí)行特定的工作。

線程生命周期

線程生命周期開始于 System.Threading.Thread 類的對(duì)象被創(chuàng)建時(shí),結(jié)束于線程被終止或完成執(zhí)行時(shí)。

下面列出了線程生命周期中的各種狀態(tài):

  • 未啟動(dòng)狀態(tài):當(dāng)線程實(shí)例被創(chuàng)建但 Start 方法未被調(diào)用時(shí)的狀況。

  • 就緒狀態(tài):當(dāng)線程準(zhǔn)備好運(yùn)行并等待 CPU 周期時(shí)的狀況。

  • 不可運(yùn)行狀態(tài):下面的幾種情況下線程是不可運(yùn)行的:

    • 已經(jīng)調(diào)用 Sleep 方法

    • 已經(jīng)調(diào)用 Wait 方法

    • 通過 I/O 操作阻塞

  • 死亡狀態(tài):當(dāng)線程已完成執(zhí)行或已中止時(shí)的狀況。

主線程

進(jìn)程中第一個(gè)被執(zhí)行的線程稱為主線程。

當(dāng) C# 程序開始執(zhí)行時(shí),主線程自動(dòng)創(chuàng)建。使用 Thread 類創(chuàng)建的線程被主線程的子線程調(diào)用。您可以使用 Thread 類的 CurrentThread 屬性訪問線程。

下面的程序演示了主線程的執(zhí)行:

實(shí)例

using System ;
using System.Threading ;

namespace MultithreadingApplication
{
    class MainThreadProgram
    {
        static void Main ( string [ ] args )
        {
            Thread th = Thread . CurrentThread ;
            th . Name = 'MainThread' ;
            Console . WriteLine ( 'This is {0}', th . Name ) ;
            Console . ReadKey ( ) ;
        }
    }
}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

This is MainThread

創(chuàng)建線程

線程是通過Thread 類創(chuàng)建的。Thread 類調(diào)用 Start() 方法來(lái)開始子線程的執(zhí)行。

下面的程序演示了這個(gè)概念:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine('Child thread starts');
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread starts

管理線程

Thread 類提供了各種管理線程的方法。

下面的實(shí)例演示了 sleep() 方法的使用,用于在一個(gè)特定的時(shí)間暫停線程。

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            Console.WriteLine('Child thread starts');
            // 線程暫停 5000 毫秒
            int sleepfor = 5000;
            Console.WriteLine('Child Thread Paused for {0} seconds',
                              sleepfor / 1000);
            Thread.Sleep(sleepfor);
            Console.WriteLine('Child thread resumes');
        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread startsChild Thread Paused for 5 secondsChild thread resumes

銷毀線程

Abort() 方法用于銷毀線程。

通過拋出 threadabortexception 在運(yùn)行時(shí)中止線程。這個(gè)異常不能被捕獲,如果有 finally 塊,控制會(huì)被送至 finally 塊。

這個(gè)異常不能被捕獲是什么鬼,可以被捕獲呀

下面的程序說明了這點(diǎn):

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        public static void CallToChildThread()
        {
            try
            {

                Console.WriteLine('Child thread starts');
                // 計(jì)數(shù)到 10
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine('Child Thread Completed');

            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine('Thread Abort Exception');
            }
            finally
            {
                Console.WriteLine('Couldn't catch the Thread Exception');
            }

        }
       
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Console.WriteLine('In Main: Creating the Child thread');
            Thread childThread = new Thread(childref);
            childThread.Start();
            // 停止主線程一段時(shí)間
            Thread.Sleep(2000);
            // 現(xiàn)在中止子線程
            Console.WriteLine('In Main: Aborting the Child thread');
            childThread.Abort();
            Console.ReadKey();
        }
    }}

當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:

In Main: Creating the Child threadChild thread starts012In Main: Aborting the Child threadThread Abort ExceptionCouldn't catch the Thread Exception

C# 多線程--愛整理

一、利用多線程提高程序性能

本節(jié)導(dǎo)讀:

隨著硬件和網(wǎng)絡(luò)的高速發(fā)展,為多線程(Multithreading)處理并行任務(wù),提供了有利條件。

其實(shí)我們每時(shí)每刻都在享受多線程帶來(lái)的便利,多核處理器多線程工作、Windows操作系統(tǒng)、Web服務(wù)器都在使用多線程工作。

使用多線程直接提高了程序的執(zhí)行效率,因此學(xué)習(xí)多線程對(duì)提高程序運(yùn)行能力非常必要,本節(jié)主要介紹多線程原理及.NET中多線程在.NET面向?qū)ο蟪绦蛟O(shè)計(jì)中的應(yīng)用。

1. 關(guān)于多線程

在介紹多線程之前,先了解一下進(jìn)程。

進(jìn)程:獨(dú)立運(yùn)行的程序稱為進(jìn)程。(比如Windows系統(tǒng)后臺(tái)程序,也可以稱為后臺(tái)進(jìn)程)

線程:對(duì)于同一個(gè)程序分為多個(gè)執(zhí)行流,稱為線程。

多線程:使用多個(gè)線程進(jìn)行多任務(wù)處理,稱為多線程。

并發(fā)是針對(duì)于單核處理器而言,但是目前市場(chǎng)上的CPU是多核的(一個(gè)芯片多個(gè)CPU核心),多線程設(shè)計(jì)可以讓多個(gè)任務(wù)分發(fā)到多個(gè)CPU上并行執(zhí)行,可以讓程序更快的執(zhí)行。但是并發(fā)通常是提高運(yùn)行在單核處理器上的程序的性能。這么說感覺有點(diǎn)違背直覺,因?yàn)樵趩魏颂幚砥魃线\(yùn)行并發(fā)程序開銷要比順序執(zhí)行該程序的開銷要大(從上到下順序執(zhí)行程序),因?yàn)椴l(fā)程序中增加了上下文切換的代價(jià)(一個(gè)線程切換到另外一個(gè)線程),從表面上看如果順序執(zhí)行所有程序反而節(jié)省了上下文切換的代價(jià)。 讓這個(gè)問題變得不同的是阻塞,程序中的某個(gè)任務(wù)因?yàn)樵摮绦蚩刂品秶獾囊恍l件(通常是I/O),整個(gè)程序就會(huì)停止下來(lái),直到外部條件發(fā)生變化。此時(shí)多線程的優(yōu)勢(shì)就會(huì)體現(xiàn)出來(lái)了,其他任務(wù)可以通過獲得CPU時(shí)間而繼續(xù)執(zhí)行,而不會(huì)讓整個(gè)程序停下來(lái)。從性能的角度來(lái)看,如果沒有線程阻塞,那么在單核處理器上使用并發(fā)那將毫無(wú)意義。

2. 如何合理使用多線程?

A.對(duì)于用戶等待程序處理時(shí),可以使用多線程處理耗時(shí)任務(wù);

B.對(duì)于一些不需要即時(shí)完成的任務(wù),可以使用后臺(tái)任務(wù)線程處理;

C.對(duì)于多并發(fā)任務(wù),可以使用多線程同時(shí)處理;

這一句的意思是讓并發(fā)線程變成并行線程嗎?即讓原本是單核處理多個(gè)線程,變成多核處理多個(gè)線程(一核分配一個(gè)線程)

D.對(duì)于通訊類,比如對(duì)線程阻塞,可以使用多線程。

除過上面的幾個(gè)常用的情況,還有很多情況下可以使用多線程。

3. 多線程的缺點(diǎn)

線程自然也有缺點(diǎn),以下列出了一些:

A.如果有大量的線程,會(huì)影響性能,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換;

B.更多的線程需要更多的內(nèi)存空間;

C.線程會(huì)給程序帶來(lái)更多的bug,因此要小心使用,比如:線程任務(wù)在執(zhí)行完成后,要及時(shí)釋放內(nèi)存;

D.線程的中止需要考慮其對(duì)程序運(yùn)行的影響。

4. .NET中的兩種多線程

.NET本身就是一個(gè)多線程的的環(huán)境。

在.NET中有兩種多線程的:

一種是使用Thread類進(jìn)行線程的創(chuàng)建、啟動(dòng),終止等操作。

一種是使用ThreadPool類用于管理線程池.

5 .NET中使用Thread進(jìn)行多線程處理

線性池是一種多線程并發(fā)的處理形式,它就是由一堆已創(chuàng)建好的線程組成。有新任務(wù) -> 取出空閑線程處理任務(wù) -> 任務(wù)處理完成放入線程池等待。避免了處理短時(shí)間任務(wù)時(shí)大量的線程重復(fù)創(chuàng)建、銷毀的代價(jià),非常適用于連續(xù)產(chǎn)生大量并發(fā)任務(wù)的場(chǎng)合。

5.1 Thread類常用方法

.NET基礎(chǔ)類庫(kù)的System.Threading命名空間提供了大量的類和接口支持多線程。System.Threading.Thread類是創(chuàng)建并控制線程,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)最為常用的類。

下面是該類幾個(gè)至關(guān)重要的方法: Thread.Start():?jiǎn)?dòng)線程的執(zhí)行; Thread.Suspend():掛起線程,或者如果線程已掛起,則不起作用; Thread.Resume():繼續(xù)已掛起的線程; Thread.Interrupt():中止處于Wait或者Sleep或者Join線程狀態(tài)的線程; Thread.Join():阻塞調(diào)用線程,直到某個(gè)線程終止時(shí)為止 Thread.Sleep():將當(dāng)前線程阻塞指定的毫秒數(shù); Thread.Abort():終止此線程。如果線程已經(jīng)在終止,則不能通過Thread.Start()來(lái)啟動(dòng)線程。

suspend 掛起、暫停 resume 繼續(xù)、重新開始 interrupt 中斷、打斷

5.2 Thread類常用屬性

Thread的屬性有很多,我們先看最常用的幾個(gè):

CurrentThread :用于獲取當(dāng)前線程;

ThreadState 當(dāng)前線程的狀態(tài)(5.4介紹);

Name:獲取或設(shè)置線程名稱;

Priority:獲取或設(shè)置線程的優(yōu)先級(jí)(5.5介紹)

ManagedThreadId:獲取當(dāng)前線程的唯一標(biāo)識(shí)

IsBackground:獲取或設(shè)置線程是前臺(tái)線程還是后臺(tái)線程(5.6介紹)

IsThreadPoolThread:獲取當(dāng)前線程是否是托管線程池(后面章節(jié)會(huì)介紹)

下面創(chuàng)建一個(gè)線程示例,來(lái)說明這幾個(gè)屬性:

Thread myThreadTest = new Thread(() =>//這里的new Thread(ThreadStart start)里面ThreadStart是一個(gè)委托,而(()=>{代碼塊...})是lambda表達(dá)式,所以可以說lambda表達(dá)式是基于委托的{
    Thread.Sleep(1000);
    Thread t = Thread.CurrentThread;
    Console.WriteLine('Name: ' + t.Name);
    Console.WriteLine('ManagedThreadId: ' + t.ManagedThreadId);
    Console.WriteLine('State: ' + t.ThreadState);
    Console.WriteLine('Priority: ' + t.Priority);
    Console.WriteLine('IsBackground: ' + t.IsBackground);
    Console.WriteLine('IsThreadPoolThread: ' + t.IsThreadPoolThread);//process 進(jìn)程}){
    Name = '線程測(cè)試',
    Priority = ThreadPriority.Highest};myThreadTest.Start();Console.WriteLine('關(guān)聯(lián)進(jìn)程的運(yùn)行的線程數(shù)量:'+System.Diagnostics.Process.GetCurrentProcess().Threads.Count);

運(yùn)行結(jié)果如下:

在這里插入圖片描述

我的天竟然有6個(gè)線程,其他四個(gè)線程是?

下面的代碼是一個(gè)小插曲,有助于強(qiáng)化理解線程委托

using System;using System.Threading;namespace MultithreadingApplication{
    delegate void P1(object n);
    delegate void P2(int n1, int n2);
    class ThreadCreationProgram
    {
        public static void MyThreadStart0()
        {
            Console.WriteLine('我的線程:0');
        }
        public static void MyThreadStart11(object n)
        {
            for (int i = 0; i < (int)n; i++)
            {
                Console.WriteLine('我的線程:' + i);
            }
        }
        public static void MyThreadStart12(int n)
        {
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine('我的線程:' + i);
            }
        }
        public static void MyThreadStart2(int n1, int n2)
        {
            for (int i = n1; i < n2; i++)
            {
                Console.WriteLine('我的線程:' + i);
            }
        }
      
        static void Main(string[] args)
        {
            //平常情況(自定義委托(參數(shù),返回值情況任意))
            P1 p1 = new P1(MyThreadStart11);
            p1(5);
            P2 p2 = new P2(MyThreadStart2);
            p2(2, 5);

            //線程情況(系統(tǒng)線程定義委托(總共兩個(gè):1無(wú)參無(wú)返回值 2有一個(gè)參無(wú)返回值)
            ThreadStart ts = new ThreadStart(MyThreadStart0);
            ts();
            ParameterizedThreadStart pts = new ParameterizedThreadStart(MyThreadStart11);//實(shí)例委托pts就相當(dāng)于一個(gè)方法指針,指向一個(gè)方法
            pts(5);

            //我的天在線程中委托的參數(shù)居然是從start()里面?zhèn)鬟M(jìn)去的
            new Thread(pts).Start(5);

            //注意下面兩個(gè)的比較
            new Thread((n) => MyThreadStart11(n)).Start(5);//lambda表達(dá)式:(n) => MyThreadStart11(n)就像當(dāng)于委托public delegate void ParameterizedThreadStart(object obj)的一個(gè)實(shí)例,所以傳進(jìn)的參數(shù)n就是object類型,下面的語(yǔ)句需要(int)n進(jìn)行強(qiáng)制轉(zhuǎn)換.
            new Thread((n) => MyThreadStart12((int)n)).Start(5);//委托的方法參數(shù)類型與委托的參數(shù)數(shù)據(jù)類型不一致,此處卻可以這樣搞,就當(dāng)是封裝的原因吧
           
           new Thread(MyThreadStart0);//甚至可以直接跟個(gè)方法名里面的括號(hào)都省略了
            Console.ReadKey();
        }
    }}

5.3 帶參數(shù)的線程方法

首先我們寫“簡(jiǎn)單線程”中無(wú)參數(shù)的方法,如下:

注意看注釋

namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static void MyThreadStart()
        {
            Console.WriteLine('我是一個(gè)簡(jiǎn)單線程');
        }
        static void Main(string[] args)
        {
            //簡(jiǎn)單的線程
            Thread myThread = new Thread(MyThreadStart);//此時(shí)的Thread(MyThreadStart)等于Thread(() =>MyThreadStart()),可能是lambda express的簡(jiǎn)寫形式
            //那么也就是說在無(wú)參方法調(diào)用委托時(shí)可以不用先實(shí)例化一個(gè)委托ThreadStart ts = new ThreadStart(MyThreadStart)然后再將ts傳進(jìn)線程Thread myThread = new Thread(ts),而可以直接在線程里傳此方法Thread myThread = new Thread(MyThreadStart),這樣的話就簡(jiǎn)單一些了
            myThread.Start();
        }     
    }}

我們使用Lambda表達(dá)式來(lái)改寫前面“簡(jiǎn)單線程”中無(wú)參數(shù)的方法,如下:

namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static void Main(string[] args)
        {
            new Thread(() => {  //此處可以說用無(wú)參的方法調(diào)用委托
                for (int i = 0; i < 5; i++)
                    Console.WriteLine('我的線程一-[{0}]', i);
            }).Start(); Console.ReadKey();
        }     
    }}

在這里插入圖片描述

上面示例創(chuàng)建的線程并沒有帶參數(shù),如果是一個(gè)有參數(shù)的方法,線程該如何創(chuàng)建?

別擔(dān)心,.NET為我們提供了一個(gè)ParameterizedThreadStart 委托 來(lái)解決帶一個(gè)參數(shù)的問題,如下:

new Thread((num) =>{
    for (int i = 0; i < (int)num; i++)
        Console.WriteLine('我的線程二--[{0}]', i);}).Start(5);/*由于ParameterizedThreadStart 委托傳的值是object類型的,所以要強(qiáng)制轉(zhuǎn)化一下*/

運(yùn)行結(jié)果如下:

在這里插入圖片描述

那么問題來(lái)了,ParameterizedThreadStart委托只有一個(gè)包含數(shù)據(jù)的參數(shù), 對(duì)于多個(gè)參數(shù)呢?我們可以使用一個(gè)無(wú)參數(shù)的方法來(lái)包裝它,如下:

先創(chuàng)建一個(gè)帶參數(shù)的方法:

static void myThreadStart(int numA, int numB){
    for (int i = (int)numA; i < (int)numB; i++)
        Console.WriteLine('我的線程三---[{0}]', i);}

然后通過無(wú)參數(shù)的委托來(lái)包裝它,如下 :

//這里默認(rèn)匹配是public delegate void ThreadStart();的一個(gè)實(shí)例new Thread(() => myThreadStart(0, 5)).Start();//事到如今我感覺可以肯定的說lambda表達(dá)式實(shí)質(zhì)上就是一個(gè)委托

運(yùn)行結(jié)果如下:

在這里插入圖片描述

5.4 Thread狀態(tài)

我們對(duì)于線程啟動(dòng)以后,如何進(jìn)行掛起和終止、重新啟用,首先線程在運(yùn)行后有一個(gè)狀態(tài)。

System.Threading.Thread.ThreadState屬性定義了執(zhí)行時(shí)線程的狀態(tài)。線程從創(chuàng)建到線程終止,它一定處于其中某一個(gè)狀態(tài)。

A.Unstarted:當(dāng)線程被創(chuàng)建時(shí),它處在Unstarted狀態(tài)。

B.Running:Thread類的Start() 方法將使線程狀態(tài)變?yōu)镽unning狀態(tài),線程將一直處于這樣的狀態(tài),除非我們調(diào)用了相應(yīng)的方法使其掛起、阻塞、銷毀或者自然終止。

C.Suspended:如果線程被掛起,它將處于Suspended狀態(tài)。

D.Running:我們調(diào)用Resume()方法使其重新執(zhí)行,這時(shí)候線程將重新變?yōu)镽unning狀態(tài)。

E.Stopped:一旦線程被銷毀或者終止,線程處于Stopped狀態(tài)。處于這個(gè)狀態(tài)的線程將不復(fù)存在,正如線程開始啟動(dòng),線程將不可能回到Unstarted狀態(tài)。

F.Background:線程還有一個(gè)Background狀態(tài),它表明線程運(yùn)行在前臺(tái)還是后臺(tái)。在一個(gè)確定的時(shí)間,線程可能處于多個(gè)狀態(tài)。

G.WaitSleepJoin、AbortRequested:舉例子來(lái)說,一個(gè)線程被調(diào)用了Sleep而處于阻塞,而接著另外一個(gè)線程調(diào)用Abort方法于這個(gè)阻塞的線程,這時(shí)候線程將同時(shí)處于WaitSleepJoin和AbortRequested狀態(tài)。

H.一旦線程響應(yīng)轉(zhuǎn)為Sleep阻塞或者中止,當(dāng)銷毀時(shí)會(huì)拋出ThreadAbortException異常。

ThreadState枚舉的10種執(zhí)行狀態(tài)如下:

在這里插入圖片描述

上圖了解一個(gè)WaitSleepJoin就可以了 monitor 監(jiān)視器,監(jiān)聽器,監(jiān)控器

對(duì)于線程阻塞和同步問題,將在下一節(jié)繼續(xù)介紹。

5.5. 線程優(yōu)先級(jí)

對(duì)于多線程任務(wù),我們可以根據(jù)其重要性和運(yùn)行所需要的資源情況,設(shè)置他的優(yōu)先級(jí) System.Threading.ThreadPriority枚舉了線程的優(yōu)先級(jí)別,從而決定了線程能夠得到多少CPU時(shí)間。

高優(yōu)先級(jí)的線程通常會(huì)比一般優(yōu)先級(jí)的線程得到更多的CPU時(shí)間

主線程與各種線程(高優(yōu)先級(jí),低優(yōu)先級(jí)線程)搶奪cup資源,一般優(yōu)先級(jí)越高搶到cup資源的概率越高,從而cpu占用的時(shí)間越多.

而不是每個(gè)線程一個(gè)一個(gè)排序來(lái),排到高優(yōu)先級(jí)線程時(shí)cup分配的時(shí)間多一些,排到低優(yōu)先級(jí)線程時(shí)cup分配的時(shí)間少一些,

新創(chuàng)建的線程優(yōu)先級(jí)為一般優(yōu)先級(jí),我們可以設(shè)置線程的優(yōu)先級(jí)別的值,如下面所示:

在這里插入圖片描述

線程搶占cpu資源可以用如下代碼測(cè)試:

static void Main(string[] args){
    int numberH1 = 0,numberH2=0, numberL1 = 0, numberL2=0;
    bool state = true;
    new Thread(() => { while (state) { numberH1++; Console.WriteLine('H1'); }; }) { Priority = ThreadPriority.Highest, Name = '線程A' }.Start();
    new Thread(() => { while (state) { numberH2++; Console.WriteLine('H2'); }; }) { Priority = ThreadPriority.Highest, Name = '線程A' }.Start();
    new Thread(() => { while (state) { numberL1++; Console.WriteLine('L1'); }; }) { Priority = ThreadPriority.Lowest, Name = '線程B' }.Start();
    //讓主線程掛件1秒
    Thread.Sleep(1000);
    state = false;
    Console.WriteLine('線程H1: {0}, 線程H2: {1}, 線程L1: {2}', numberH1,numberH2,numberL1);
    Console.ReadKey();}

5.6 前臺(tái)線程和后臺(tái)線程

線程有兩種,默認(rèn)情況下為前臺(tái)線程,要想設(shè)置為后臺(tái)線程也非常容易,只需要加一個(gè)屬性:thread.IsBackground = true;就可以變?yōu)橐粋€(gè)后臺(tái)線程了。

重點(diǎn)來(lái)了,前后臺(tái)線程的區(qū)別:

A.前臺(tái)線程:應(yīng)用程序必須執(zhí)行完所有的前臺(tái)線程才能退出;

B.后臺(tái)線程:應(yīng)用程序不必考慮其是否全部完成,可以直接退出。應(yīng)用程序退出時(shí),自動(dòng)終止后臺(tái)線程。

下面我們使用一個(gè)輸出從0到1000的數(shù)字,來(lái)實(shí)驗(yàn)一下前臺(tái)線程和后臺(tái)線程的區(qū)別:

static void Main(string[] args){
    Thread myThread = new Thread(() => { for (int i = 0; i < 1000; i++) Console.WriteLine(i); });

    var key = Console.ReadLine();
    if (key == '1')
    {
        myThread.IsBackground = true;
        myThread.Start();
    }
    else
    {
        myThread.IsBackground = false;
        myThread.Start();
    }}

如果輸入1(后臺(tái)線程),線程會(huì)很快關(guān)閉,并不會(huì)等輸出完1000個(gè)數(shù)字再關(guān)閉;

如果輸入其它(前臺(tái)線程),回車后,則線程會(huì)等1000個(gè)數(shù)字輸出完后,窗口關(guān)閉;

6. 本節(jié)要點(diǎn):

A.本節(jié)主要介紹了線程的基本知識(shí);

B.Thread常用的屬性、方法;

C.Thread委托的方法有多個(gè)參數(shù)的用法;

D.Thread的優(yōu)先級(jí);

E.Thread的執(zhí)行狀態(tài);

F.前臺(tái)線程和后臺(tái)線程;

后面會(huì)繼續(xù)深入介紹利用線程提高程序性能。

二、多線程高級(jí)應(yīng)用

本節(jié)要點(diǎn):

上節(jié)介紹了多線程的基本使用方法和基本應(yīng)用示例,本節(jié)深入介紹.NET多線程中的高級(jí)應(yīng)用。

主要有在線程資源共享中的線程安全和線程沖突的解決方案;多線程同步,使用線程鎖和線程通知實(shí)現(xiàn)線程同步。

1、 ThreadStatic特性

特性:[ThreadStatic]

功能:指定靜態(tài)字段在不同線程中擁有不同的值

在此之前,我們先看一個(gè)多線程的示例:

我們定義一個(gè)靜態(tài)字段:

static int num = 0;new Thread(() =>{
    for (int i = 0; i < 1000000; i++)
        ++num;
    Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線程一' }.Start();隱藏代碼new Thread(() =>{
    for (int i = 0; i < 2000000; i++)
        ++num;
    Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線程二' }.Start();

運(yùn)行多次結(jié)果如下:

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

可以看到,三次的運(yùn)行結(jié)果均不相同,產(chǎn)生這種問題的原因是多線程中同步共享問題導(dǎo)致的,即是多個(gè)線程同時(shí)共享了一個(gè)資源。

此處代碼與上下文無(wú)關(guān),知識(shí)兩個(gè)疑惑,注意看注釋

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {       
        static int num = 0;
        static void Main(string[] args)
        {
            new Thread(() =>
            {
                int k=0;
                for (k = 0; k < 100000; k++) 
                    ++num;
                Console.WriteLine('來(lái)自{0}:{1} 此時(shí)k的值為{2}', Thread.CurrentThread.Name, num,k);
            })
            { Name = '線程一' }.Start();

            new Thread(() =>
            {
                int j = 0;
                for (; j < 200000; j++)
                    ++num;
                Console.WriteLine('來(lái)自{0}:{1} 此時(shí)j的值為{2}', Thread.CurrentThread.Name, num,j);
            })
            { Name = '線程二' }.Start();

            Thread.Sleep(5*1000);
            Console.WriteLine('主線程又開始');
            Console.ReadKey();
        }
    }
    //疑惑 1 兩個(gè)線程執(zhí)行次數(shù)竟然大于3000000,也竟然有小于3000000的   
    //2 竟然會(huì)輸出:來(lái)自線程一:56265   答;這是因?yàn)椴粌H只有兩個(gè)線程在執(zhí)行,還有個(gè)主線程在執(zhí)行,不要忽略了.
    //因?yàn)橹骶€程走到了Console.ReadKey(),所以會(huì)在控制臺(tái)輸出線程一還未走完的num值,此時(shí)num值也就小于1000000了
    //那么為了避免主線程對(duì)子線程的影響可以阻塞主線程一段時(shí)間知道子線程完成(用sleep方法)--我的天吶我發(fā)現(xiàn)排除了主線程readkey的干擾后
    //仍然會(huì)輸出:來(lái)自線程一:989265的情況者,這發(fā)生了什么?}

如何解決上述問題,最簡(jiǎn)單的方法就是使用靜態(tài)字段的ThreadStatic特性。

在定義靜態(tài)字段時(shí),加上[ThreadStatic]特性,如下:

[ThreadStatic]static int num = 0;

兩個(gè)線程不變的情況下,再次運(yùn)行,結(jié)果如下:

在這里插入圖片描述

不論運(yùn)行多少次,結(jié)果都是一樣的,當(dāng)字段被ThreadStatic特性修飾后,它的值在每個(gè)線程中都是不同的,即每個(gè)線程對(duì)static字段都會(huì)重新分配內(nèi)存空間,就當(dāng)然于一次new操作,這樣一來(lái),由于static字段所產(chǎn)生的問題也就沒有了。

2. 資源共享

多線程的資源共享,也就是多線程同步(即資源同步),需要注意的是線程同步指的是線程所訪問的資源同步,并非是線程本身的同步。

在實(shí)際使用多線程的過程中,并非都是各個(gè)線程訪問不同的資源。

下面看一個(gè)線程示例,假如我們并不知道線程要多久完成,我們等待一個(gè)固定的時(shí)間(假如是500毫秒):

先定義一個(gè)靜態(tài)字段:

static int result;Thread myThread = new Thread(() =>{
    Thread.Sleep(1000);
    result = 100;});myThread.Start();Thread.Sleep(500);             Console.WriteLine(result);

運(yùn)行結(jié)果如下:

在這里插入圖片描述

可以看到結(jié)果是0,顯然不是我們想要的,但往往在線程執(zhí)行過程中,我們并不知道它要多久完成,能不能在線程完成后有一個(gè)通知?

下面的代碼與上下文無(wú)關(guān),只是一個(gè)小注意點(diǎn)

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static int result;
        static void Main(string[] args)
        {
           
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(10000);
                result = 100;
                Console.WriteLine(result);
                Console.ReadKey();
            });
            myThread.Start();//這一步再往下走兩個(gè)線程就開始搶奪cup資源了
            Thread.Sleep(1000);
            Console.WriteLine(result);
            Console.ReadKey();//執(zhí)行完這一步,并不會(huì)就一直停在這里,當(dāng)myThread線程睡眠時(shí)間到了,會(huì)自動(dòng)執(zhí)行myThread線程
           //然后停在myThread的Readkey處,在控制臺(tái)輸入任意值,走到24,再輸入任意值,走到18.
        }
    }}

.NET為我們提供了一個(gè)Join方法,就是線程阻塞,可以解決上述問題,我們使用Stopwatch來(lái)記時(shí)

改進(jìn)線程代碼如下:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        static int result;
        static void Main(string[] args)
        {
            //Diagnostic 診斷.   Stopwatch 跑表   
            //StartNew()初始化新的 System.Diagnostics.Stopwatch 實(shí)例,將運(yùn)行時(shí)間屬性設(shè)置為零,然后開始測(cè)量運(yùn)行時(shí)間。
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(1000);
                result = 100;
            });
            myThread.Start();
            Thread.Sleep(500);//走到這一步主線程睡眠,進(jìn)入子線程myThread
            myThread.Join();//0.5秒鐘后回到主線程這一步,走到這一步時(shí)會(huì)停下來(lái)直到子線程myThread執(zhí)行完畢.
            Console.WriteLine(watch.ElapsedMilliseconds);//Elapsed 消逝、過去    Millisecond 毫秒
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }}//Join()和sleep()都是線程阻塞

運(yùn)行結(jié)果如下:

在這里插入圖片描述

結(jié)果和我們想要的是一致的。

3. 線程鎖

除了上面示例的方法,對(duì)于線程同步,.NET還為我們提供了一個(gè)鎖機(jī)制來(lái)解決同步,再次改進(jìn)上面示例如下:

using System;using System.Threading;namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        //先定義一個(gè)靜態(tài)字段來(lái)存儲(chǔ)鎖
        static object locker = new object();
        static int result;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread t1 = new Thread(() =>
            {
                lock (locker)//lock (x)里面的x是引用類型
                {
                    Thread.Sleep(10000);
                    result = 100;
                }
            });
            t1.Start();
            Thread.Sleep(5000);
            lock (locker)//lock(x)中的x是同一個(gè)引用類型的變量時(shí),這些鎖之間是互斥的,只有最先執(zhí)行的鎖執(zhí)行完,才會(huì)執(zhí)行下一個(gè)鎖
            {
                Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds);
                Console.WriteLine('線程輸出:' + result);
            }
            Console.ReadKey();
        }
    }}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

運(yùn)行結(jié)果和上面示例一樣,如果線程處理過程較復(fù)雜,可以看到耗時(shí)明顯減少,這是一種用比阻塞更效率的方式完成線程同步。

4. 線程通知

前面說到了能否在一個(gè)線程完成后,通知等待的線程呢,這里.NET為我們提供了一個(gè)事件通知的方法來(lái)解決這個(gè)問題。

4.1 AutoResetEvent

改進(jìn)上面的線程如下:

using System;using System.Threading;//一個(gè)線程完成后通知另外一個(gè)線程(是一個(gè)!與下面的幾個(gè)不同)namespace MultithreadingApplication{
    class ThreadCreationProgram
    {
        //先定義一個(gè)通知對(duì)象                                     //EventWaitHandle 表示一個(gè)線程同步事件。         
        static EventWaitHandle tellMe = new AutoResetEvent(false);//里面的boolean該值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。
        static int result = 0;                              
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThread = new Thread(() =>
            {
                Thread.Sleep(5000);
                result = 100;
                tellMe.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。
            });
            myThread.Start();
            tellMe.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。
            Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds);
            Console.WriteLine('線程輸出:' + result);
        }
    }//待在同一個(gè)代碼塊的兩個(gè)線程是資源共享的,即兩個(gè)線程是同步的}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

4.2 ManualResetEvent

和AutoResetEvent 相對(duì)的還有一個(gè) ManualResetEvent 手動(dòng)模式,他們的區(qū)別在于,在線程結(jié)束后ManualResetEvent 還是可以通行的,除非手動(dòng)Reset關(guān)閉。下面看一個(gè)示例:

這句話的意思是在mre.Set()和mre.WaitOne()執(zhí)行完之后,如果有另一個(gè)mre.WaitOne(),此時(shí)仍可以通過.如果是AutoResetEvent的話就不可以了,可以將下面的代碼ManualResetEvent改成AutoResetEvent試一下

using System;using System.Threading;namespace MultithreadingApplication{
    //一個(gè)線程完成後通知其他個(gè)線程.(其他的意思是多于一個(gè))
    class ThreadCreationProgram
    {
        //EventWaitHandle 表示一個(gè)線程同步事件。
        static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。
        static int result = 0;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThreadFirst = new Thread(() =>
            {
                Thread.Sleep(10000);
                result = 100;
                mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。
            })
            { Name = '線程一' };
            Thread myThreadSecond = new Thread(() =>
            {
            	兩個(gè)WaitOne()執(zhí)行后進(jìn)入線程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過
                mre.WaitOne();
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線程二' };
            myThreadFirst.Start();
            myThreadSecond.Start();
            mre.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。
            Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')');
            Console.WriteLine('線程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')');
            Console.ReadKey();
        }
    }//手動(dòng)Reset關(guān)閉,mre.Reset();}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

線程二獲取結(jié)果:100可能先輸出,或者在中間輸出,也可能最后輸出,這取決于主線程與線程二對(duì)cpu資源的搶奪

下面代碼是手動(dòng) Reset()關(guān)閉展示

using System;using System.Threading;namespace MultithreadingApplication{
    //一個(gè)線程完成後通知其他個(gè)線程.(其他的意思是多于一個(gè))
    class ThreadCreationProgram
    {
        //EventWaitHandle 表示一個(gè)線程同步事件。
        static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類。
        static int result = 0;
        static void Main(string[] args)
        {
            System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
            Thread myThreadFirst = new Thread(() =>
            {
                Thread.Sleep(1000);
                result = 100;
                mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),從而允許一個(gè)或多個(gè)等待線程繼續(xù)執(zhí)行。
               
            })
            { Name = '線程一' };
            Thread myThreadSecond = new Thread(() =>
            {
                
                mre.WaitOne();//兩個(gè)WaitOne()同時(shí)執(zhí)行后進(jìn)入線程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過
                
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線程二' };

            Thread myThreadThird = new Thread(() =>
            {
                mre.Reset();
                mre.WaitOne();                
                Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')');
            })
            { Name = '線程三' };

            myThreadFirst.Start();
            myThreadSecond.Start();
            mre.WaitOne();//阻止當(dāng)前線程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào)。
            Console.WriteLine('線程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')');
            Console.WriteLine('線程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')');

            myThreadThird.Start();
            Thread.Sleep(1000);
            mre.Set();//將這一句注釋掉線程三WaitOne()就等不到信號(hào),從而會(huì)被一直阻塞.
            Console.ReadKey();
        }
    }}

4.3. Semaphore

Semaphore也是線程通知的一種,上面的通知模式,在線程開啟的數(shù)量很多的情況下,使用Reset()關(guān)閉時(shí),如果不使用Sleep休眠一下,很有可能導(dǎo)致某些線程沒有恢復(fù)的情況下,某一線程提前關(guān)閉,對(duì)于這種很難預(yù)測(cè)的情況,.NET提供了更高級(jí)的通知方式Semaphore,可以保證在超多線程時(shí)不會(huì)出現(xiàn)上述問題。

using System;using System.Threading;//semaphor 發(fā)信號(hào),打旗語(yǔ)namespace MultithreadingApplication{
    class Program
    {
        //先定義一個(gè)通知對(duì)象的靜態(tài)字段
        //Semaphore(初始授予1個(gè)請(qǐng)求數(shù),設(shè)置最大可授予5個(gè)請(qǐng)求數(shù))
        static Semaphore semaphore = new Semaphore(1, 5);//初始授予1個(gè)請(qǐng)求數(shù),如果沒有semaphore.Release()語(yǔ)句,則只會(huì)執(zhí)行一個(gè)子線程,執(zhí)行完之后請(qǐng)求數(shù)又會(huì)變成0
        static void Main(string[] args)
        {
            for (int i = 1; i <= 5; i++)
            {
                Thread thread = new Thread(Work);
                thread.Start(i);
            }
            Thread.Sleep(2000);
            //授予3個(gè)請(qǐng)求
            semaphore.Release(3);
            Console.ReadLine();
        }
        static void Work(object obj)
        {
            semaphore.WaitOne();
            Console.WriteLine('print: {0}', obj);
        }
    }//程序執(zhí)行完畢會(huì)輸出四個(gè)記錄}

5. 本節(jié)要點(diǎn):

A.線程中靜態(tài)字段的ThreadStatic特性,使用該字段在不同線程中擁有不同的值

B.線程同步的幾種方式,線程鎖和線程通知

C.線程通知的兩種方式:AutoResetEvent /ManualResetEvent 和 Semaphore

多線程的更多特性,下一節(jié)繼續(xù)深入介紹。

三、利用多線程提高程序性能(下)

本節(jié)導(dǎo)讀:

上節(jié)說了線程同步中使用線程鎖和線程通知的方式來(lái)處理資源共享問題,這些是多線程的基本原理。

.NET 4.0 以后對(duì)多線程的實(shí)現(xiàn)變得更簡(jiǎn)單了。

本節(jié)主要討論 .NET4.0 多線程的新特性——使用 Task類創(chuàng)建多線程。

讀前必備:

A. LINQ使用 [.net 面向?qū)ο缶幊袒A(chǔ)] (20) LINQ使用

B. 泛型 [.net 面向?qū)ο缶幊袒A(chǔ)] (18) 泛型

1.線程池ThreadPool

在介紹4.0以后的多線程新特征之前,先簡(jiǎn)單說一下線程池。

通過前面對(duì)多線程的學(xué)習(xí),我們發(fā)現(xiàn)多線程的創(chuàng)建和使用并不難,難的在于多線程的管理,特別是線程數(shù)量級(jí)很多的情況下,如何進(jìn)行管理和資源釋放。需要使用線程池來(lái)解決。

簡(jiǎn)單來(lái)說線程池就是.NET提供的存放線程的一個(gè)對(duì)象容器。

為什么要使用線性池

微軟官網(wǎng)說法如下:許多應(yīng)用程序創(chuàng)建大量處于睡眠狀態(tài),等待事件發(fā)生的線程。還有許多線程可能會(huì)進(jìn)入休眠狀態(tài),這些線程只是為了定期喚醒以輪詢更改或更新的狀態(tài)信息。 線程池,使您可以通過由系統(tǒng)管理的工作線程池來(lái)更有效地使用線程。

說得簡(jiǎn)單一點(diǎn),每新建一個(gè)線程都需要占用內(nèi)存空間和其他資源,而新建了那么多線程,有很多在休眠,或者在等待資源釋放;又有許多線程只是周期性的做一些小工作,如刷新數(shù)據(jù)等等,太浪費(fèi)了,劃不來(lái),實(shí)際編程中大量線程突發(fā),然后在短時(shí)間內(nèi)結(jié)束的情況很少見。于是,就提出了線程池的概念。線程池中的線程執(zhí)行完指定的方法后并不會(huì)自動(dòng)消除,而是以掛起狀態(tài)返回線程池,如果應(yīng)用程序再次向線程池發(fā)出請(qǐng)求,那么處以掛起狀態(tài)的線程就會(huì)被激活并執(zhí)行任務(wù),而不會(huì)創(chuàng)建新線程,這就節(jié)約了很多開銷。只有當(dāng)線程數(shù)達(dá)到最大線程數(shù)量,系統(tǒng)才會(huì)自動(dòng)銷毀線程。因此,使用線程池可以避免大量的創(chuàng)建和銷毀的開支,具有更好的性能和穩(wěn)定性,其次,開發(fā)人員把線程交給系統(tǒng)管理,可以集中精力處理其他任務(wù)。

線程池線程分為兩類:工作線程和 IO 線程 . 線程池是一種多線程處理形式,處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)。

下面是一個(gè)線程池的示例:

using System;using System.Threading;namespace MultithreadingApplication{
    class Program
    {
        //先設(shè)置一個(gè)創(chuàng)建線程總數(shù)靜態(tài)字段:
        static readonly int totalThreads = 20; 
        static void Main(string[] args)
        {
            //線性池是靜態(tài)類可以直接使用
            //參數(shù)1:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小工作程序線程數(shù)。
            //參數(shù)2:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小空閑異步 I/O 線程數(shù)。
            ThreadPool.SetMinThreads(2, 2);
            //參數(shù)1:線程池中輔助線程的最大數(shù)目。
            //參數(shù)2:線程池中異步 I/O 線程的最大數(shù)目。
            ThreadPool.SetMaxThreads(20, 20);
            for (int i = 0; i < totalThreads; i++)
            {
                ThreadPool.QueueUserWorkItem(o =>
                {
                    Thread.Sleep(1000);
                    int a, b;
                    //參數(shù)1:可用輔助線程的數(shù)目。
                    //參數(shù)2:可用異步 I/O 線程的數(shù)目。
                    ThreadPool.GetAvailableThreads(out a, out b);
                    Console.WriteLine(string.Format('({0}/{1}) #{2} : {3}', a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
                });
            }
            Console.WriteLine('主線程完成');
            Console.ReadKey();
        }
    }}

2. Task類

用 ThreadPool 的 QueueUserWorkItem() 方法發(fā)起一次異步的線程執(zhí)行很簡(jiǎn)單,但是該方法最大的問題是沒有一個(gè)內(nèi)建的機(jī)制讓你知道操作什么時(shí)候完成,有沒有一個(gè)內(nèi)建的機(jī)制在操作完成后獲得一個(gè)返回值。為此, 在.NET 4.0 以后,我們 可以使用 System.Threading.Tasks 中的 Task 類。 這也是.NET 4.0 以后多線程的推薦做法。

構(gòu)造一個(gè) Task<T> 對(duì)象,并為泛型 T 參數(shù)傳遞一個(gè)操作的返回類型。

Task類可以使用多種方法創(chuàng)建多線程,下面詳細(xì)介紹。

2.1 使用Factory屬性

Task 實(shí)例可以用各種不同的方式創(chuàng)建。 最常見的方法是使用任務(wù)的 Factory 屬性檢索可用來(lái)創(chuàng)建用于多個(gè)用途的 TaskFactory 實(shí)例。

例如,要?jiǎng)?chuàng)建運(yùn)行操作的 Task ,可以使用工廠的 StartNew 方法:

//最簡(jiǎn)單的線程示例Task.Factory.StartNew(() =>{
    Console.WriteLine('我是使用Factory屬性創(chuàng)建的線程');});

如果想簡(jiǎn)單的創(chuàng)建一個(gè)Task,那么使用Factory.StartNew()來(lái)創(chuàng)建,很簡(jiǎn)便。

如果像對(duì)所創(chuàng)建的Task附加更多的定制和設(shè)置特定的屬性,請(qǐng)繼續(xù)往下看。

2.2 使用Task實(shí)例實(shí)現(xiàn)多線程

//簡(jiǎn)單的Task實(shí)例創(chuàng)建線程Action<object> action = (object obj) =>{
    Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);};//上面的是簡(jiǎn)寫形式,也可以寫成下面的形式.//Action<object> action = new Action<object>((object obj) =>//{//    Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);//});Task t1 = new Task(action, '參數(shù)');t1.Start();

運(yùn)行結(jié)果如下:

在這里插入圖片描述

//簡(jiǎn)寫上面實(shí)例,并創(chuàng)建100個(gè)線程System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();int m = 100;Task[] tasks = new Task[m];for (int i = 0; i < m; i++){
    tasks[i] = new Task((object obj) =>
        {
            Thread.Sleep(200);
            Console.WriteLine('Task={0}, obj={1}, Thread={2},當(dāng)前時(shí)間:{3}',
            Task.CurrentId, obj.ToString(),
            Thread.CurrentThread.ManagedThreadId,
            System.DateTime.Now.ToString());
        }, '參數(shù)' + i.ToString()   //public Task(Action<object> action, object state);
    );
    tasks[i].Start();//線程開始}
           Task.WaitAll(tasks);  //等待提供的所有 System.Threading.Tasks.Task 對(duì)象完成執(zhí)行過程。Console.WriteLine('線程耗時(shí):{0},當(dāng)前時(shí)間:{1}' ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString());

這里task創(chuàng)建的100個(gè)線程貌似是異步執(zhí)行的

運(yùn)行結(jié)果如下:

在這里插入圖片描述

2.3 Task傳入?yún)?shù)

上面介紹了使用一個(gè)Action委托來(lái)完成線程,那么給線程中傳入?yún)?shù),就可以使用System.Action<object>來(lái)完成。

傳入一個(gè)參數(shù)的示例:

/// <summary>/// 一個(gè)參數(shù)的方法/// </summary>/// <param name='parameter'></param>static void MyMethod(string parameter){
    Console.WriteLine('{0}', parameter);}

調(diào)用如下:

//Task傳入一個(gè)參數(shù)Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), 'aaa');myTask.Start();

傳入多個(gè)參數(shù)如下:

/// <summary>/// 多個(gè)參數(shù)的方法/// </summary>/// <param name='parameter1'></param>/// <param name='parameter2'></param>/// <param name='parameter3'></param>static void MyMethod(string parameter1,int parameter2,DateTime parameter3){
    Console.WriteLine('{0} {1} {2}', parameter1,parameter2.ToString(),parameter3.ToString());}

調(diào)用如下:

//Task傳入多個(gè)參數(shù)for (int i = 1; i <= 20; i++){              
    new Task(() => { MyMethod('我的線程', i, DateTime.Now); }).Start();
    Thread.Sleep(200);}

運(yùn)行結(jié)果如下:

在這里插入圖片描述

對(duì)于傳入多個(gè)參數(shù),可以使用無(wú)參數(shù)委托包裝一個(gè)多參數(shù)的方法來(lái)完成。

2.4 Task的結(jié)果

要獲取Task的結(jié)果,在創(chuàng)建Task的時(shí)候,就要采用Task<T>來(lái)實(shí)例化一個(gè)Task。

其中的T就是Task執(zhí)行完成之后返回結(jié)果的類型。

通過Task實(shí)例的Result屬性就可以獲取結(jié)果。

System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();Task<int> myTask = new Task<int>(() =>//這里面泛型委托修飾符out表示協(xié)變{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;});myTask.Start();           Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTask.Result,watch.ElapsedMilliseconds);

這里task創(chuàng)建線程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線程

運(yùn)行結(jié)果如下:

在這里插入圖片描述

使用Factory屬性來(lái)完成上面的示例:

//使用Factory屬性創(chuàng)建System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;});            Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTaskSecond.Result, watchSecond.ElapsedMilliseconds);

這里task創(chuàng)建線程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線程

運(yùn)行結(jié)果如下:

在這里插入圖片描述

多線程除以上的一些基礎(chǔ)知識(shí),在處理各種并行任務(wù)和多核編程中的使用,小伙伴可以參考專門關(guān)于多線程的書籍學(xué)習(xí)。

想要完全深入的學(xué)習(xí)多線程需要慢慢修煉,不斷積累。

3. 本節(jié)要點(diǎn):

A.本點(diǎn)簡(jiǎn)單介紹了線程池ThreadPool的使用;

B.介紹一使用Task進(jìn)行多線程創(chuàng)建及Tast的參數(shù)傳入和返回結(jié)果。

線程一些小知識(shí)點(diǎn)

并發(fā)與并行
例子:當(dāng)你要吃飯又要玩游戲
順序執(zhí)行:先吃完飯?jiān)偻嬗螒?br> 并發(fā):吃口飯玩一會(huì)游戲
并行:邊吃飯邊玩游戲

異步
與同步相對(duì)應(yīng),當(dāng)一個(gè)異步過程調(diào)用發(fā)出后,調(diào)用者在沒有得到結(jié)果之前,就可以繼續(xù)執(zhí)行后續(xù)操作。當(dāng)這個(gè)調(diào)用完成后,一般通過狀態(tài)、通知和回調(diào)來(lái)通知調(diào)用者。對(duì)于異步調(diào)用,調(diào)用的返回并不受調(diào)用者控制。

同步
同步多線程資源共享;Java中所有方法都是同步調(diào)用,應(yīng)為必須要等到結(jié)果后才會(huì)繼續(xù)執(zhí)行。
簡(jiǎn)單來(lái)說,同步就是必須一件一件事做,等前一件做完了才能做下一件事。

Thread ThreadPool和Task
Thread就是Thread,需要自己調(diào)度,適合長(zhǎng)跑型的操作。

ThreadPool是Thread基礎(chǔ)上的一個(gè)線程池,目的是減少頻繁創(chuàng)建線程的開銷。線程很貴,要開新的stack,要增加CPU上下文切換,所以ThreadPool適合頻繁、短期執(zhí)行的小操作。調(diào)度算法是自適應(yīng)的,會(huì)根據(jù)程序執(zhí)行的模式調(diào)整配置,通常不需要自己調(diào)度線程。另外分為Worker和IO兩個(gè)池。

Task或者說TPL是一個(gè)更上層的封裝,NB之處在于continuation。continuation的意義在于:高性能的程序通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫這種高scalability的代碼。Task會(huì)根據(jù)一些flag,比如是不是long-running來(lái)決定底層用Thread還是ThreadPool 

結(jié)論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細(xì)節(jié),比如告訴Task是不是long-running;比如盡量別Wait;再比如IO之后的continuation要盡快結(jié)束然后把線程還回去,有事開個(gè)Worker做,要不然會(huì)影響后面的IO,等等。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多