欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#多線程用法詳解

 更新時(shí)間:2021年12月18日 15:30:09   作者:.NET開(kāi)發(fā)菜鳥(niǎo)  
本文詳細(xì)講解了C#多線程用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、基本概念

1、進(jìn)程

首先打開(kāi)任務(wù)管理器,查看當(dāng)前運(yùn)行的進(jìn)程:

從任務(wù)管理器里面可以看到當(dāng)前所有正在運(yùn)行的進(jìn)程。那么究竟什么是進(jìn)程呢?

進(jìn)程(Process)是Windows系統(tǒng)中的一個(gè)基本概念,它包含著一個(gè)運(yùn)行程序所需要的資源。一個(gè)正在運(yùn)行的應(yīng)用程序在操作系統(tǒng)中被視為一個(gè)進(jìn)程,進(jìn)程可以包括一個(gè)或多個(gè)線程。線程是操作系統(tǒng)分配處理器時(shí)間的基本單元,在進(jìn)程中可以有多個(gè)線程同時(shí)執(zhí)行代碼。進(jìn)程之間是相對(duì)獨(dú)立的,一個(gè)進(jìn)程無(wú)法訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)(除非利用分布式計(jì)算方式),一個(gè)進(jìn)程運(yùn)行的失敗也不會(huì)影響其他進(jìn)程的運(yùn)行,Windows系統(tǒng)就是利用進(jìn)程把工作劃分為多個(gè)獨(dú)立的區(qū)域的。進(jìn)程可以理解為一個(gè)程序的基本邊界。是應(yīng)用程序的一個(gè)運(yùn)行例程,是應(yīng)用程序的一次動(dòng)態(tài)執(zhí)行過(guò)程。

2、線程

在任務(wù)管理器里面查詢當(dāng)前總共運(yùn)行的線程數(shù):

線程(Thread)是進(jìn)程中的基本執(zhí)行單元,是操作系統(tǒng)分配CPU時(shí)間的基本單位,一個(gè)進(jìn)程可以包含若干個(gè)線程,在進(jìn)程入口執(zhí)行的第一個(gè)線程被視為這個(gè)進(jìn)程的主線程。在.NET應(yīng)用程序中,都是以Main()方法作為入口的,當(dāng)調(diào)用此方法時(shí)系統(tǒng)就會(huì)自動(dòng)創(chuàng)建一個(gè)主線程。線程主要是由CPU寄存器、調(diào)用棧和線程本地存儲(chǔ)器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當(dāng)前所執(zhí)行線程的狀態(tài),調(diào)用棧主要用于維護(hù)線程所調(diào)用到的內(nèi)存與數(shù)據(jù),TLS主要用于存放線程的狀態(tài)信息。

二、多線程

多線程的優(yōu)點(diǎn):可以同時(shí)完成多個(gè)任務(wù);可以使程序的響應(yīng)速度更快;可以讓占用大量處理時(shí)間的任務(wù)或當(dāng)前沒(méi)有進(jìn)行處理的任務(wù)定期將處理時(shí)間讓給別的任務(wù);可以隨時(shí)停止任務(wù);可以設(shè)置每個(gè)任務(wù)的優(yōu)先級(jí)以優(yōu)化程序性能。

那么可能有人會(huì)問(wèn):為什么可以多線程執(zhí)行呢?總結(jié)起來(lái)有下面兩方面的原因:

1、CPU運(yùn)行速度太快,硬件處理速度跟不上,所以操作系統(tǒng)進(jìn)行分時(shí)間片管理。這樣,從宏觀角度來(lái)說(shuō)是多線程并發(fā)的,因?yàn)镃PU速度太快,察覺(jué)不到,看起來(lái)是同一時(shí)刻執(zhí)行了不同的操作。但是從微觀角度來(lái)講,同一時(shí)刻只能有一個(gè)線程在處理。

2、目前電腦都是多核多CPU的,一個(gè)CPU在同一時(shí)刻只能運(yùn)行一個(gè)線程,但是多個(gè)CPU在同一時(shí)刻就可以運(yùn)行多個(gè)線程。

然而,多線程雖然有很多優(yōu)點(diǎn),但是也必須認(rèn)識(shí)到多線程可能存在影響系統(tǒng)性能的不利方面,才能正確使用線程。不利方面主要有如下幾點(diǎn):

  • (1)線程也是程序,所以線程需要占用內(nèi)存,線程越多,占用內(nèi)存也越多。
  • (2)多線程需要協(xié)調(diào)和管理,所以需要占用CPU時(shí)間以便跟蹤線程。
  • (3)線程之間對(duì)共享資源的訪問(wèn)會(huì)相互影響,必須解決爭(zhēng)用共享資源的問(wèn)題。
  • (4)線程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多程序缺陷。

當(dāng)啟動(dòng)一個(gè)可執(zhí)行程序時(shí),將創(chuàng)建一個(gè)主線程。在默認(rèn)的情況下,C#程序具有一個(gè)線程,此線程執(zhí)行程序中以Main方法開(kāi)始和結(jié)束的代碼,Main()方法直接或間接執(zhí)行的每一個(gè)命令都有默認(rèn)線程(主線程)執(zhí)行,當(dāng)Main()方法返回時(shí)此線程也將終止。

一個(gè)進(jìn)程可以創(chuàng)建一個(gè)或多個(gè)線程以執(zhí)行與該進(jìn)程關(guān)聯(lián)的部分程序代碼。在C#中,線程是使用Thread類處理的,該類在System.Threading命名空間中。使用Thread類創(chuàng)建線程時(shí),只需要提供線程入口,線程入口告訴程序讓這個(gè)線程做什么。通過(guò)實(shí)例化一個(gè)Thread類的對(duì)象就可以創(chuàng)建一個(gè)線程。創(chuàng)建新的Thread對(duì)象時(shí),將創(chuàng)建新的托管線程。Thread類接收一個(gè)ThreadStart委托或ParameterizedThreadStart委托的構(gòu)造函數(shù),該委托包裝了調(diào)用Start方法時(shí)由新線程調(diào)用的方法,示例代碼如下:

Thread thread=new Thread(new ThreadStart(method));//創(chuàng)建線程
thread.Start(); //啟動(dòng)線程

上面代碼實(shí)例化了一個(gè)Thread對(duì)象,并指明將要調(diào)用的方法method(),然后啟動(dòng)線程。ThreadStart委托中作為參數(shù)的方法不需要參數(shù),并且沒(méi)有返回值。ParameterizedThreadStart委托一個(gè)對(duì)象作為參數(shù),利用這個(gè)參數(shù)可以很方便地向線程傳遞參數(shù),示例代碼如下:

Thread thread=new Thread(new ParameterizedThreadStart(method));//創(chuàng)建線程

thread.Start(3); //啟動(dòng)線程

創(chuàng)建多線程的步驟:

  • 1、編寫線程所要執(zhí)行的方法
  • 2、實(shí)例化Thread類,并傳入一個(gè)指向線程所要執(zhí)行方法的委托。(這時(shí)線程已經(jīng)產(chǎn)生,但還沒(méi)有運(yùn)行)
  • 3、調(diào)用Thread實(shí)例的Start方法,標(biāo)記該線程可以被CPU執(zhí)行了,但具體執(zhí)行時(shí)間由CPU決定

2.1 System.Threading.Thread類

Thread類是是控制線程的基礎(chǔ)類,位于System.Threading命名空間下,具有4個(gè)重載的構(gòu)造函數(shù):

名稱 說(shuō)明
Thread(ParameterizedThreadStart) 初始化 Thread 類的新實(shí)例,指定允許對(duì)象在線程啟動(dòng)時(shí)傳遞給線程的委托。要執(zhí)行的方法是有參的。
Thread(ParameterizedThreadStart,?Int32) 初始化 Thread 類的新實(shí)例,指定允許對(duì)象在線程啟動(dòng)時(shí)傳遞給線程的委托,并指定線程的最大堆棧大小
Thread(ThreadStart) 初始化 Thread 類的新實(shí)例。要執(zhí)行的方法是無(wú)參的。
Thread(ThreadStart,?Int32) 初始化 Thread 類的新實(shí)例,指定線程的最大堆棧大小。

ThreadStart是一個(gè)無(wú)參的、返回值為void的委托。委托定義如下:

public delegate void ThreadStart()

通過(guò)ThreadStart委托創(chuàng)建并運(yùn)行一個(gè)線程:

class Program
    {
        static void Main(string[] args)
        {
            //創(chuàng)建無(wú)參的線程
            Thread thread1 = new Thread(new ThreadStart(Thread1));
            //調(diào)用Start方法執(zhí)行線程
            thread1.Start();

            Console.ReadKey();
        }

        /// <summary>
        /// 創(chuàng)建無(wú)參的方法
        /// </summary>
        static void Thread1()
        {
            Console.WriteLine("這是無(wú)參的方法");
        }
    }

運(yùn)行結(jié)果

除了可以運(yùn)行靜態(tài)的方法,還可以運(yùn)行實(shí)例方法

class Program
    {
        static void Main(string[] args)
        {
            //創(chuàng)建ThreadTest類的一個(gè)實(shí)例
            ThreadTest test=new ThreadTest();
            //調(diào)用test實(shí)例的MyThread方法
            Thread thread = new Thread(new ThreadStart(test.MyThread));
            //啟動(dòng)線程
            thread.Start();
            Console.ReadKey();
        }
    }

    class ThreadTest
    {
        public void MyThread()
        {
            Console.WriteLine("這是一個(gè)實(shí)例方法");
        }
    }

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

如果為了簡(jiǎn)單,也可以通過(guò)匿名委托或Lambda表達(dá)式來(lái)為Thread的構(gòu)造方法賦值

static void Main(string[] args)
 {
       //通過(guò)匿名委托創(chuàng)建
       Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通過(guò)匿名委托創(chuàng)建的線程"); });
       thread1.Start();
       //通過(guò)Lambda表達(dá)式創(chuàng)建
       Thread thread2 = new Thread(() => Console.WriteLine("我是通過(guò)Lambda表達(dá)式創(chuàng)建的委托"));
       thread2.Start();
       Console.ReadKey();
 }

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

ParameterizedThreadStart是一個(gè)有參的、返回值為void的委托,定義如下:

public delegate void ParameterizedThreadStart(Object obj)

class Program
    {
        static void Main(string[] args)
        {
            //通過(guò)ParameterizedThreadStart創(chuàng)建線程
            Thread thread = new Thread(new ParameterizedThreadStart(Thread1));
            //給方法傳值
            thread.Start("這是一個(gè)有參數(shù)的委托");
            Console.ReadKey();
        }

        /// <summary>
        /// 創(chuàng)建有參的方法
        /// 注意:方法里面的參數(shù)類型必須是Object類型
        /// </summary>
        /// <param name="obj"></param>
        static void Thread1(object obj)
        {
            Console.WriteLine(obj);
        }
    }

注意:ParameterizedThreadStart委托的參數(shù)類型必須是Object的。如果使用的是不帶參數(shù)的委托,不能使用帶參數(shù)的Start方法運(yùn)行線程,否則系統(tǒng)會(huì)拋出異常。但使用帶參數(shù)的委托,可以使用thread.Start()來(lái)運(yùn)行線程,這時(shí)所傳遞的參數(shù)值為null。

2.2線程的常用屬性

屬性名稱 說(shuō)明
CurrentContext 獲取線程正在其中執(zhí)行的當(dāng)前上下文。
CurrentThread 獲取當(dāng)前正在運(yùn)行的線程。
ExecutionContext 獲取一個(gè) ExecutionContext 對(duì)象,該對(duì)象包含有關(guān)當(dāng)前線程的各種上下文的信息。
IsAlive 獲取一個(gè)值,該值指示當(dāng)前線程的執(zhí)行狀態(tài)。
IsBackground 獲取或設(shè)置一個(gè)值,該值指示某個(gè)線程是否為后臺(tái)線程。
IsThreadPoolThread 獲取一個(gè)值,該值指示線程是否屬于托管線程池。
ManagedThreadId 獲取當(dāng)前托管線程的唯一標(biāo)識(shí)符。
Name 獲取或設(shè)置線程的名稱。
Priority 獲取或設(shè)置一個(gè)值,該值指示線程的調(diào)度優(yōu)先級(jí)。
ThreadState 獲取一個(gè)值,該值包含當(dāng)前線程的狀態(tài)。

2.2.1 線程的標(biāo)識(shí)符

ManagedThreadId是確認(rèn)線程的唯一標(biāo)識(shí)符,程序在大部分情況下都是通過(guò)Thread.ManagedThreadId來(lái)辨別線程的。而Name是一個(gè)可變值,在默認(rèn)時(shí)候,Name為一個(gè)空值 Null,開(kāi)發(fā)人員可以通過(guò)程序設(shè)置線程的名稱,但這只是一個(gè)輔助功能。

2.2.2 線程的優(yōu)先級(jí)別

當(dāng)線程之間爭(zhēng)奪CPU時(shí)間時(shí),CPU按照線程的優(yōu)先級(jí)給予服務(wù)。高優(yōu)先級(jí)的線程可以完全阻止低優(yōu)先級(jí)的線程執(zhí)行。.NET為線程設(shè)置了Priority屬性來(lái)定義線程執(zhí)行的優(yōu)先級(jí)別,里面包含5個(gè)選項(xiàng),其中Normal是默認(rèn)值。除非系統(tǒng)有特殊要求,否則不應(yīng)該隨便設(shè)置線程的優(yōu)先級(jí)別。

成員名稱 說(shuō)明
Lowest 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之后。
BelowNormal 可以將 Thread 安排在具有 Normal 優(yōu)先級(jí)的線程之后,在具有 Lowest 優(yōu)先級(jí)的線程之前。
Normal 默認(rèn)選擇。可以將 Thread 安排在具有 AboveNormal 優(yōu)先級(jí)的線程之后,在具有 BelowNormal 優(yōu)先級(jí)的線程之前。
AboveNormal 可以將 Thread 安排在具有 Highest 優(yōu)先級(jí)的線程之后,在具有 Normal 優(yōu)先級(jí)的線程之前。
Highest 可以將 Thread 安排在具有任何其他優(yōu)先級(jí)的線程之前。

2.2.3 線程的狀態(tài)

通過(guò)ThreadState可以檢測(cè)線程是處于Unstarted、Sleeping、Running 等等狀態(tài),它比 IsAlive 屬性能提供更多的特定信息。

前面說(shuō)過(guò),一個(gè)應(yīng)用程序域中可能包括多個(gè)上下文,而通過(guò)CurrentContext可以獲取線程當(dāng)前的上下文。

CurrentThread是最常用的一個(gè)屬性,它是用于獲取當(dāng)前運(yùn)行的線程。

2.2.4 System.Threading.Thread的方法

Thread 中包括了多個(gè)方法來(lái)控制線程的創(chuàng)建、掛起、停止、銷毀,以后來(lái)的例子中會(huì)經(jīng)常使用。

方法名稱 說(shuō)明
Abort() 終止本線程。
GetDomain() 返回當(dāng)前線程正在其中運(yùn)行的當(dāng)前域。
GetDomainId() 返回當(dāng)前線程正在其中運(yùn)行的當(dāng)前域Id。
Interrupt() 中斷處于 WaitSleepJoin 線程狀態(tài)的線程。
Join() 已重載。 阻塞調(diào)用線程,直到某個(gè)線程終止時(shí)為止。
Resume() 繼續(xù)運(yùn)行已掛起的線程。
Start() 執(zhí)行本線程。
Suspend() 掛起當(dāng)前線程,如果當(dāng)前線程已屬于掛起狀態(tài)則此不起作用
Sleep() 把正在運(yùn)行的線程掛起一段時(shí)間。

線程示例

static void Main(string[] args)
        {
            //獲取正在運(yùn)行的線程
            Thread thread = Thread.CurrentThread;
            //設(shè)置線程的名字
            thread.Name = "主線程";
            //獲取當(dāng)前線程的唯一標(biāo)識(shí)符
            int id = thread.ManagedThreadId;
            //獲取當(dāng)前線程的狀態(tài)
            ThreadState state= thread.ThreadState;
            //獲取當(dāng)前線程的優(yōu)先級(jí)
            ThreadPriority priority= thread.Priority;
            string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
                "Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
                state, priority);

            Console.WriteLine(strMsg);

            Console.ReadKey();
        }

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

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

前臺(tái)線程:只有所有的前臺(tái)線程都結(jié)束,應(yīng)用程序才能結(jié)束。默認(rèn)情況下創(chuàng)建的線程都是前臺(tái)線程。

后臺(tái)線程:只要所有的前臺(tái)線程結(jié)束,后臺(tái)線程自動(dòng)結(jié)束。通過(guò)Thread.IsBackground設(shè)置后臺(tái)線程。必須在調(diào)用Start方法之前設(shè)置線程的類型,否則一旦線程運(yùn)行,將無(wú)法改變其類型。

通過(guò)BeginXXX方法運(yùn)行的線程都是后臺(tái)線程。

class Program
    {
        static void Main(string[] args)
        {
            //演示前臺(tái)、后臺(tái)線程
            BackGroundTest background = new BackGroundTest(10);
            //創(chuàng)建前臺(tái)線程
            Thread fThread = new Thread(new ThreadStart(background.RunLoop));
            //給線程命名
            fThread.Name = "前臺(tái)線程";


            BackGroundTest background1 = new BackGroundTest(20);
            //創(chuàng)建后臺(tái)線程
            Thread bThread = new Thread(new ThreadStart(background1.RunLoop));
            bThread.Name = "后臺(tái)線程";
            //設(shè)置為后臺(tái)線程
            bThread.IsBackground = true;

            //啟動(dòng)線程
            fThread.Start();
            bThread.Start();
        }
    }

    class BackGroundTest
    {
        private int Count;
        public BackGroundTest(int count)
        {
            this.Count = count;
        }
        public void RunLoop()
        {
            //獲取當(dāng)前線程的名稱
            string threadName = Thread.CurrentThread.Name;
            for (int i = 0; i < Count; i++)
            {
                Console.WriteLine("{0}計(jì)數(shù):{1}",threadName,i.ToString());
                //線程休眠500毫秒
                Thread.Sleep(1000);
            }
            Console.WriteLine("{0}完成計(jì)數(shù)",threadName);

        }
    }

運(yùn)行結(jié)果:前臺(tái)線程執(zhí)行完,后臺(tái)線程未執(zhí)行完,程序自動(dòng)結(jié)束。

bThread.IsBackground = true注釋掉,運(yùn)行結(jié)果:主線程執(zhí)行完畢后(Main函數(shù)),程序并未結(jié)束,而是要等所有的前臺(tái)線程結(jié)束以后才會(huì)結(jié)束。

后臺(tái)線程一般用于處理不重要的事情,應(yīng)用程序結(jié)束時(shí),后臺(tái)線程是否執(zhí)行完成對(duì)整個(gè)應(yīng)用程序沒(méi)有影響。如果要執(zhí)行的事情很重要,需要將線程設(shè)置為前臺(tái)線程。

2.4 線程同步

所謂同步:是指在某一時(shí)刻只有一個(gè)線程可以訪問(wèn)變量。

如果不能確保對(duì)變量的訪問(wèn)是同步的,就會(huì)產(chǎn)生錯(cuò)誤。

c#為同步訪問(wèn)變量提供了一個(gè)非常簡(jiǎn)單的方式,即使用c#語(yǔ)言的關(guān)鍵字Lock,它可以把一段代碼定義為互斥段,互斥段在一個(gè)時(shí)刻內(nèi)只允許一個(gè)線程進(jìn)入執(zhí)行,而其他線程必須等待。在c#中,關(guān)鍵字Lock定義如下:

Lock(expression)
{
statement_block
}

expression代表你希望跟蹤的對(duì)象:

如果你想保護(hù)一個(gè)類的實(shí)例,一般地,你可以使用this;

如果你想保護(hù)一個(gè)靜態(tài)變量(如互斥代碼段在一個(gè)靜態(tài)方法內(nèi)部),一般使用類名就可以了

而statement_block就算互斥段的代碼,這段代碼在一個(gè)時(shí)刻內(nèi)只可能被一個(gè)線程執(zhí)行。

以書店賣書為例

class Program
    {
        static void Main(string[] args)
        {
            BookShop book = new BookShop();
            //創(chuàng)建兩個(gè)線程同時(shí)訪問(wèn)Sale方法
            Thread t1 = new Thread(new ThreadStart(book.Sale));
            Thread t2 = new Thread(new ThreadStart(book.Sale));
            //啟動(dòng)線程
            t1.Start();
            t2.Start();
            Console.ReadKey();
        }
    }



    class BookShop
    {
        //剩余圖書數(shù)量
        public int num = 1;
        public void Sale()
        {
            int tmp = num;
            if (tmp > 0)//判斷是否有書,如果有就可以賣
            {
                Thread.Sleep(1000);
                num -= 1;
                Console.WriteLine("售出一本圖書,還剩余{0}本", num);
            }
            else
            {
                Console.WriteLine("沒(méi)有了");
            }
        }
    }

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

從運(yùn)行結(jié)果可以看出,兩個(gè)線程同步訪問(wèn)共享資源,沒(méi)有考慮同步的問(wèn)題,結(jié)果不正確。

考慮線程同步,改進(jìn)后的代碼:

class Program
    {
        static void Main(string[] args)
        {
            BookShop book = new BookShop();
            //創(chuàng)建兩個(gè)線程同時(shí)訪問(wèn)Sale方法
            Thread t1 = new Thread(new ThreadStart(book.Sale));
            Thread t2 = new Thread(new ThreadStart(book.Sale));
            //啟動(dòng)線程
            t1.Start();
            t2.Start();
            Console.ReadKey();
        }
    }



    class BookShop
    {
        //剩余圖書數(shù)量
        public int num = 1;
        public void Sale()
        {
            //使用lock關(guān)鍵字解決線程同步問(wèn)題
            lock (this)
            {
                int tmp = num;
                if (tmp > 0)//判斷是否有書,如果有就可以賣
                {
                    Thread.Sleep(1000);
                    num -= 1;
                    Console.WriteLine("售出一本圖書,還剩余{0}本", num);
                }
                else
                {
                    Console.WriteLine("沒(méi)有了");
                }
            }
        }
    }

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

2.5 跨線程訪問(wèn)

點(diǎn)擊“測(cè)試”,創(chuàng)建一個(gè)線程,從0循環(huán)到10000給文本框賦值,代碼如下:

private void btn_Test_Click(object sender, EventArgs e)
        {
            //創(chuàng)建一個(gè)線程去執(zhí)行這個(gè)方法:創(chuàng)建的線程默認(rèn)是前臺(tái)線程
            Thread thread = new Thread(new ThreadStart(Test));
            //Start方法標(biāo)記這個(gè)線程就緒了,可以隨時(shí)被執(zhí)行,具體什么時(shí)候執(zhí)行這個(gè)線程,由CPU決定
            //將線程設(shè)置為后臺(tái)線程
            thread.IsBackground = true;
            thread.Start();
        }

        private void Test()
        {
            for (int i = 0; i < 10000; i++)
            {
                this.textBox1.Text = i.ToString();
            }
        }

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

產(chǎn)生錯(cuò)誤的原因:textBox1是由主線程創(chuàng)建的,thread線程是另外創(chuàng)建的一個(gè)線程,在.NET上執(zhí)行的是托管代碼,C#強(qiáng)制要求這些代碼必須是線程安全的,即不允許跨線程訪問(wèn)Windows窗體的控件。

解決方案:

1、在窗體的加載事件中,將C#內(nèi)置控件(Control)類的CheckForIllegalCrossThreadCalls屬性設(shè)置為false,屏蔽掉C#編譯器對(duì)跨線程調(diào)用的檢查。

 private void Form1_Load(object sender, EventArgs e)
 {
        //取消跨線程的訪問(wèn)
        Control.CheckForIllegalCrossThreadCalls = false;
 }

使用上述的方法雖然可以保證程序正常運(yùn)行并實(shí)現(xiàn)應(yīng)用的功能,但是在實(shí)際的軟件開(kāi)發(fā)中,做如此設(shè)置是不安全的(不符合.NET的安全規(guī)范),在產(chǎn)品軟件的開(kāi)發(fā)中,此類情況是不允許的。如果要在遵守.NET安全標(biāo)準(zhǔn)的前提下,實(shí)現(xiàn)從一個(gè)線程成功地訪問(wèn)另一個(gè)線程創(chuàng)建的空間,要使用C#的方法回調(diào)機(jī)制。

2、使用回調(diào)函數(shù)

回調(diào)實(shí)現(xiàn)的一般過(guò)程:

C#的方法回調(diào)機(jī)制,也是建立在委托基礎(chǔ)上的,下面給出它的典型實(shí)現(xiàn)過(guò)程。

(1)、定義、聲明回調(diào)。

//定義回調(diào)
private delegate void DoSomeCallBack(Type para);
//聲明回調(diào)
DoSomeCallBack doSomaCallBack;

可以看出,這里定義聲明的“回調(diào)”(doSomaCallBack)其實(shí)就是一個(gè)委托。

(2)、初始化回調(diào)方法。

doSomeCallBack=new DoSomeCallBack(DoSomeMethod);

所謂“初始化回調(diào)方法”實(shí)際上就是實(shí)例化剛剛定義了的委托,這里作為參數(shù)的DoSomeMethod稱為“回調(diào)方法”,它封裝了對(duì)另一個(gè)線程中目標(biāo)對(duì)象(窗體控件或其他類)的操作代碼。

(3)、觸發(fā)對(duì)象動(dòng)作

Opt obj.Invoke(doSomeCallBack,arg);

其中Opt obj為目標(biāo)操作對(duì)象,在此假設(shè)它是某控件,故調(diào)用其Invoke方法。Invoke方法簽名為:

object Control.Invoke(Delegate method,params object[] args);

它的第一個(gè)參數(shù)為委托類型,可見(jiàn)“觸發(fā)對(duì)象動(dòng)作”的本質(zhì),就是把委托doSomeCallBack作為參數(shù)傳遞給控件的Invoke方法,這與委托的使用方式是一模一樣的。

最終作用于對(duì)象Opt obj的代碼是置于回調(diào)方法體DoSomeMethod()中的,如下所示:

private void DoSomeMethod(type para)
{
//方法體
Opt obj.someMethod(para);
}

如果不用回調(diào),而是直接在程序中使用“Opt obj.someMethod(para);”,則當(dāng)對(duì)象Opt obj不在本線程(跨線程訪問(wèn))時(shí)就會(huì)發(fā)生上面所示的錯(cuò)誤。

從以上回調(diào)實(shí)現(xiàn)的一般過(guò)程可知:C#的回調(diào)機(jī)制,實(shí)質(zhì)上是委托的一種應(yīng)用。在C#網(wǎng)絡(luò)編程中,回調(diào)的應(yīng)用是非常普遍的,有了方法回調(diào),就可以在.NET上寫出線程安全的代碼了。

使用方法回調(diào),實(shí)現(xiàn)給文本框賦值:

namespace MultiThreadDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //定義回調(diào)
        private delegate void setTextValueCallBack(int value);
        //聲明回調(diào)
        private setTextValueCallBack setCallBack;

        private void btn_Test_Click(object sender, EventArgs e)
        {
            //實(shí)例化回調(diào)
            setCallBack = new setTextValueCallBack(SetValue);
            //創(chuàng)建一個(gè)線程去執(zhí)行這個(gè)方法:創(chuàng)建的線程默認(rèn)是前臺(tái)線程
            Thread thread = new Thread(new ThreadStart(Test));
            //Start方法標(biāo)記這個(gè)線程就緒了,可以隨時(shí)被執(zhí)行,具體什么時(shí)候執(zhí)行這個(gè)線程,由CPU決定
            //將線程設(shè)置為后臺(tái)線程
            thread.IsBackground = true;
            thread.Start();
        }

        private void Test()
        {
            for (int i = 0; i < 10000; i++)
            {
                //使用回調(diào)
                textBox1.Invoke(setCallBack, i);
            }
        }

        /// <summary>
        /// 定義回調(diào)使用的方法
        /// </summary>
        /// <param name="value"></param>
        private void SetValue(int value)
        {
            this.textBox1.Text = value.ToString();
        }
    }
}

2.6 終止線程

若想終止正在運(yùn)行的線程,可以使用Abort()方法。

三、同步和異步

同步和異步是對(duì)方法執(zhí)行順序的描述。

同步:等待上一行完成計(jì)算之后,才會(huì)進(jìn)入下一行。

例如:請(qǐng)同事吃飯,同事說(shuō)很忙,然后就等著同事忙完,然后一起去吃飯。

異步:不會(huì)等待方法的完成,會(huì)直接進(jìn)入下一行,是非阻塞的。

例如:請(qǐng)同事吃飯,同事說(shuō)很忙,那同事先忙,自己去吃飯,同事忙完了他自己去吃飯。

下面通過(guò)一個(gè)例子講解同步和異步的區(qū)別

1、新建一個(gè)winform程序,上面有兩個(gè)按鈕,一個(gè)同步方法、一個(gè)異步方法,在屬性里面把輸出類型改成控制臺(tái)應(yīng)用程序,這樣可以看到輸出結(jié)果,代碼如下:

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 MyAsyncThreadDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 異步方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAsync_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"***************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}");
            Action<string> action = this.DoSomethingLong;
            // 調(diào)用委托(同步調(diào)用)
            action.Invoke("btnAsync_Click_1");
            // 異步調(diào)用委托
            action.BeginInvoke("btnAsync_Click_2",null,null);
            Console.WriteLine($"***************btnAsync_Click End    {Thread.CurrentThread.ManagedThreadId}");
        }

        /// <summary>
        /// 同步方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSync_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"****************btnSync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            int j = 3;
            int k = 5;
            int m = j + k;
            for (int i = 0; i < 5; i++)
            {
                string name = string.Format($"btnSync_Click_{i}");
                this.DoSomethingLong(name);
            }
        }


        private void DoSomethingLong(string name)
        {
            Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            long lResult = 0;
            for (int i = 0; i < 1000000000; i++)
            {
                lResult += i;
            }
            Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
        }
    }
}

2、啟動(dòng)程序,點(diǎn)擊同步,結(jié)果如下:

從上面的截圖中能夠很清晰的看出:同步方法是等待上一行代碼執(zhí)行完畢之后才會(huì)執(zhí)行下一行代碼。

點(diǎn)擊異步,結(jié)果如下:

從上面的截圖中看出:當(dāng)執(zhí)行到action.BeginInvoke("btnAsync_Click_2",null,null);這句代碼的時(shí)候,程序并沒(méi)有等待這段代碼執(zhí)行完就執(zhí)行了下面的End,沒(méi)有阻塞程序的執(zhí)行。

在剛才的測(cè)試中,如果點(diǎn)擊同步,這時(shí)winform界面不能拖到,界面卡住了,是因?yàn)橹骶€程(即UI線程)在忙于計(jì)算。

點(diǎn)擊異步的時(shí)候,界面不會(huì)卡住,這是因?yàn)橹骶€程已經(jīng)結(jié)束,計(jì)算任務(wù)交給子線程去做。

在仔細(xì)檢查上面兩個(gè)截圖,可以看出異步的執(zhí)行速度比同步執(zhí)行速度要快。同步方法執(zhí)行完將近16秒,異步方法執(zhí)行完將近6秒。

在看下面的一個(gè)例子,修改異步的方法,也和同步方法一樣執(zhí)行循環(huán),修改后的代碼如下:

private void btnAsync_Click(object sender, EventArgs e)
{
      Console.WriteLine($"***************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId}");
      //Action<string> action = this.DoSomethingLong;
      //// 調(diào)用委托(同步調(diào)用)
      //action.Invoke("btnAsync_Click_1");
      //// 異步調(diào)用委托
      //action.BeginInvoke("btnAsync_Click_2",null,null);
      Action<string> action = this.DoSomethingLong;
      for (int i = 0; i < 5; i++)
      {
           //Thread.Sleep(5);
           string name = string.Format($"btnAsync_Click_{i}");
           action.BeginInvoke(name, null, null);
      }
      Console.WriteLine($"***************btnAsync_Click End    {Thread.CurrentThread.ManagedThreadId}");
}

結(jié)果如下:

從截圖中能夠看出:同步方法執(zhí)行是有序的,異步方法執(zhí)行是無(wú)序的。異步方法無(wú)序包括啟動(dòng)無(wú)序和結(jié)束無(wú)序。啟動(dòng)無(wú)序是因?yàn)橥粫r(shí)刻向操作系統(tǒng)申請(qǐng)線程,操作系統(tǒng)收到申請(qǐng)以后,返回執(zhí)行的順序是無(wú)序的,所以啟動(dòng)是無(wú)序的。結(jié)束無(wú)序是因?yàn)殡m然線程執(zhí)行的是同樣的操作,但是每個(gè)線程的耗時(shí)是不同的,所以結(jié)束的時(shí)候不一定是先啟動(dòng)的線程就先結(jié)束。從上面同步方法中可以清晰的看出:btnSync_Click_0執(zhí)行時(shí)間耗時(shí)不到3秒,而btnSync_Click_1執(zhí)行時(shí)間耗時(shí)超過(guò)了3秒??梢韵胂篌w育比賽中的跑步,每位運(yùn)動(dòng)員聽(tīng)到發(fā)令槍起跑的順序不同,每位運(yùn)動(dòng)員花費(fèi)的時(shí)間不同,最終到達(dá)終點(diǎn)的順序也不同。

總結(jié)一下同步方法和異步方法的區(qū)別:

  • 1、同步方法由于主線程忙于計(jì)算,所以會(huì)卡住界面。
  • 異步方法由于主線程執(zhí)行完了,其他計(jì)算任務(wù)交給子線程去執(zhí)行,所以不會(huì)卡住界面,用戶體驗(yàn)性好。
  • 2、同步方法由于只有一個(gè)線程在計(jì)算,所以執(zhí)行速度慢。
  • 異步方法由多個(gè)線程并發(fā)運(yùn)算,所以執(zhí)行速度快,但并不是線性增長(zhǎng)的(資源可能不夠)。多線程也不是越多越好,只有多個(gè)獨(dú)立的任務(wù)同時(shí)運(yùn)行,才能加快速度。
  • 3、同步方法是有序的。
  • 異步多線程是無(wú)序的:?jiǎn)?dòng)無(wú)序,執(zhí)行時(shí)間不確定,所以結(jié)束也是無(wú)序的。一定不要通過(guò)等待幾毫秒的形式來(lái)控制線程啟動(dòng)/執(zhí)行時(shí)間/結(jié)束。

四、回調(diào)

先來(lái)看看異步多線程無(wú)序的例子:

在界面上新增一個(gè)按鈕,實(shí)現(xiàn)代碼如下:

private void btnAsyncAdvanced_Click(object sender, EventArgs e)
{
      Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
      Action<string> action = this.DoSomethingLong;
      action.BeginInvoke("btnAsyncAdvanced_Click", null, null);
      // 需求:異步多線程執(zhí)行完之后再打印出下面這句
      Console.WriteLine($"到這里計(jì)算已經(jīng)完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
      Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

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

從上面的截圖中看出,最終的效果并不是我們想要的效果,而且打印輸出的還是主線程。

既然異步多線程是無(wú)序的,那我們有沒(méi)有什么辦法可以解決無(wú)序的問(wèn)題呢?辦法當(dāng)然是有的,那就是使用回調(diào),.NET框架已經(jīng)幫我們實(shí)現(xiàn)了回調(diào):

BeginInvoke的第二個(gè)參數(shù)就是一個(gè)回調(diào),那么AsyncCallback究竟是什么呢?F12查看AsyncCallback的定義:

發(fā)現(xiàn)AsyncCallback就是一個(gè)委托,參數(shù)類型是IAsyncResult,明白了AsyncCallback是什么以后,將上面的代碼進(jìn)行如下的改造:

private void btnAsyncAdvanced_Click(object sender, EventArgs e)
{
    Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
    Action<string> action = this.DoSomethingLong;
    // 定義一個(gè)回調(diào)
    AsyncCallback callback = p =>
    {
       Console.WriteLine($"到這里計(jì)算已經(jīng)完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
    };
    // 回調(diào)作為參數(shù)
    action.BeginInvoke("btnAsyncAdvanced_Click", callback, null);
    Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 }

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

上面的截圖中可以看出,這就是我們想要的效果,而且打印是子線程輸出的,但是程序究竟是怎么實(shí)現(xiàn)的呢?我們可以進(jìn)行如下的猜想:

程序執(zhí)行到BeginInvoke的時(shí)候,會(huì)申請(qǐng)一個(gè)基于線程池的線程,這個(gè)線程會(huì)完成委托的執(zhí)行(在這里就是執(zhí)行DoSomethingLong()方法),在委托執(zhí)行完以后,這個(gè)線程又會(huì)去執(zhí)行callback回調(diào)的委托,執(zhí)行callback委托需要一個(gè)IAsyncResult類型的參數(shù),這個(gè)IAsyncResult類型的參數(shù)是如何來(lái)的呢?鼠標(biāo)右鍵放到BeginInvoke上面,查看返回值:

發(fā)現(xiàn)BeginInvoke的返回值就是IAsyncResult類型的。那么這個(gè)返回值是不是就是callback委托的參數(shù)呢?將代碼進(jìn)行如下的修改:

private void btnAsyncAdvanced_Click(object sender, EventArgs e)
{
            // 需求:異步多線程執(zhí)行完之后再打印出下面這句
            Console.WriteLine($"****************btnAsyncAdvanced_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            Action<string> action = this.DoSomethingLong;
            // 無(wú)序的
            //action.BeginInvoke("btnAsyncAdvanced_Click", null, null);

            IAsyncResult asyncResult = null;
            // 定義一個(gè)回調(diào)
            AsyncCallback callback = p =>
            {
                // 比較兩個(gè)變量是否是同一個(gè)
                Console.WriteLine(object.ReferenceEquals(p,asyncResult));
                Console.WriteLine($"到這里計(jì)算已經(jīng)完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
            };
            // 回調(diào)作為參數(shù)
            asyncResult= action.BeginInvoke("btnAsyncAdvanced_Click", callback, null);
            Console.WriteLine($"****************btnAsyncAdvanced_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

結(jié)果:

這里可以看出BeginInvoke的返回值就是callback委托的參數(shù)。

現(xiàn)在我們可以使用回調(diào)解決異步多線程無(wú)序的問(wèn)題了。

2、獲取委托異步調(diào)用的返回值

使用EndInvoke可以獲取委托異步調(diào)用的返回值,請(qǐng)看下面的例子:

private void btnAsyncReturnVlaue_Click(object sender, EventArgs e)
{
       // 定義一個(gè)無(wú)參數(shù)、int類型返回值的委托
       Func<int> func = () =>
       {
             Thread.Sleep(2000);
             return DateTime.Now.Day;
       };
       // 輸出委托同步調(diào)用的返回值
       Console.WriteLine($"func.Invoke()={func.Invoke()}");
       // 委托的異步調(diào)用
       IAsyncResult asyncResult = func.BeginInvoke(p =>
       {
            Console.WriteLine(p.AsyncState);
       },"異步調(diào)用返回值");
       // 輸出委托異步調(diào)用的返回值
       Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(asyncResult)}");
}

結(jié)果:

以上所述是小編給大家介紹的C#多線程用法詳解,希望對(duì)大家有所幫助。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!

相關(guān)文章

  • C#新特性之可空引用類型

    C#新特性之可空引用類型

    本文詳細(xì)講解了C#新特性之可空引用類型,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • C#中的Dialog對(duì)話框

    C#中的Dialog對(duì)話框

    這篇文章介紹了C#中的Dialog對(duì)話框,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • 常用類之TCP連接類-socket編程

    常用類之TCP連接類-socket編程

    常用類之TCP連接類-socket編程...
    2007-03-03
  • C# 三種序列化方法分享

    C# 三種序列化方法分享

    這篇文章主要介紹了C# 三種序列化方法,需要的朋友可以參考下
    2014-02-02
  • WPF實(shí)現(xiàn)窗體中的懸浮按鈕

    WPF實(shí)現(xiàn)窗體中的懸浮按鈕

    這篇文章主要為大家詳細(xì)介紹了WPF實(shí)現(xiàn)窗體中的懸浮按鈕,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • c# 如何用組合替代繼承

    c# 如何用組合替代繼承

    這篇文章主要介紹了c# 如何用組合替代繼承,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-02-02
  • C#中Timer使用及解決重入問(wèn)題

    C#中Timer使用及解決重入問(wèn)題

    本文主要介紹了C#中Timer使用及解決重入問(wèn)題的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • C#實(shí)現(xiàn)判斷當(dāng)前操作用戶管理角色的方法

    C#實(shí)現(xiàn)判斷當(dāng)前操作用戶管理角色的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)判斷當(dāng)前操作用戶管理角色的方法,涉及C#針對(duì)系統(tǒng)用戶判斷的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-08-08
  • C#調(diào)用Python的URL接口的示例

    C#調(diào)用Python的URL接口的示例

    這篇文章主要介紹了C#調(diào)用Python的URL接口的示例,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下
    2020-10-10
  • C#實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出任一Word圖表的通用呈現(xiàn)方法

    C#實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出任一Word圖表的通用呈現(xiàn)方法

    應(yīng)人才測(cè)評(píng)產(chǎn)品的需求,導(dǎo)出測(cè)評(píng)報(bào)告是其中一個(gè)重要的環(huán)節(jié),報(bào)告的文件類型也多種多樣,其中WORD輸出也扮演了一個(gè)重要的角色,本文給大家介紹了C#實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出任一Word圖表的通用呈現(xiàn)方法及一些體會(huì),需要的朋友可以參考下
    2023-10-10

最新評(píng)論