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

深入學(xué)習(xí)C#多線(xiàn)程

 更新時(shí)間:2022年02月24日 15:49:39   作者:.NET開(kāi)發(fā)菜鳥(niǎo)  
本文詳細(xì)講解了C#多線(xiàn)程編程的相關(guān)技術(shù),文中通過(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è)線(xiàn)程。線(xiàn)程是操作系統(tǒng)分配處理器時(shí)間的基本單元,在進(jìn)程中可以有多個(gè)線(xiàn)程同時(shí)執(zhí)行代碼。進(jìn)程之間是相對(duì)獨(dú)立的,一個(gè)進(jìn)程無(wú)法訪(fǎng)問(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、線(xiàn)程

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

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

二、多線(xiàn)程

多線(xiàn)程的優(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í)以?xún)?yōu)化程序性能。

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

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

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

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

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

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

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

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

thread.Start(); //啟動(dòng)線(xiàn)程

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

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

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

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

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

2.1 System.Threading.Thread類(lèi)

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

名稱(chēng)說(shuō)明
Thread(ParameterizedThreadStart)

初始化 Thread 類(lèi)的新實(shí)例,指定允許對(duì)象在線(xiàn)程啟動(dòng)時(shí)傳遞給線(xiàn)程的委托。要執(zhí)行的方法是有參的。

Thread(ParameterizedThreadStart, Int32)初始化 Thread 類(lèi)的新實(shí)例,指定允許對(duì)象在線(xiàn)程啟動(dòng)時(shí)傳遞給線(xiàn)程的委托,并指定線(xiàn)程的最大堆棧大小
Thread(ThreadStart)

初始化 Thread 類(lèi)的新實(shí)例。要執(zhí)行的方法是無(wú)參的。

Thread(ThreadStart, Int32)

初始化 Thread 類(lèi)的新實(shí)例,指定線(xiàn)程的最大堆棧大小。

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

public delegate void ThreadStart()

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

class Program
    {
        static void Main(string[] args)
        {
            //創(chuàng)建無(wú)參的線(xiàn)程
            Thread thread1 = new Thread(new ThreadStart(Thread1));
            //調(diào)用Start方法執(zhí)行線(xiàn)程
            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類(lèi)的一個(gè)實(shí)例
            ThreadTest test=new ThreadTest();
            //調(diào)用test實(shí)例的MyThread方法
            Thread thread = new Thread(new ThreadStart(test.MyThread));
            //啟動(dòng)線(xiàn)程
            thread.Start();
            Console.ReadKey();
        }
    }

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

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

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

static void Main(string[] args)
 {
       //通過(guò)匿名委托創(chuàng)建
       Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通過(guò)匿名委托創(chuàng)建的線(xiàn)程"); });
       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)建線(xiàn)程
            Thread thread = new Thread(new ParameterizedThreadStart(Thread1));
            //給方法傳值
            thread.Start("這是一個(gè)有參數(shù)的委托");
            Console.ReadKey();
        }

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

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

2.2 線(xiàn)程的常用屬性

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

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

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

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

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

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

2.2.3 線(xiàn)程的狀態(tài)

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

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

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

2.2.4 System.Threading.Thread的方法

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

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

線(xiàn)程示例

static void Main(string[] args)
        {
            //獲取正在運(yùn)行的線(xiàn)程
            Thread thread = Thread.CurrentThread;
            //設(shè)置線(xiàn)程的名字
            thread.Name = "主線(xiàn)程";
            //獲取當(dāng)前線(xiàn)程的唯一標(biāo)識(shí)符
            int id = thread.ManagedThreadId;
            //獲取當(dāng)前線(xiàn)程的狀態(tài)
            ThreadState state= thread.ThreadState;
            //獲取當(dāng)前線(xiàn)程的優(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)線(xiàn)程和后臺(tái)線(xiàn)程

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

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

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

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


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

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

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

        }
    }

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

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

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

2.4 線(xiàn)程同步

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

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

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

Lock(expression)
{
   statement_block
}

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

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

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

以書(shū)店賣(mài)書(shū)為例

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



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

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

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

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

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



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

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

2.5 跨線(xiàn)程訪(fǎng)問(wèn)

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

private void btn_Test_Click(object sender, EventArgs e)
        {
            //創(chuàng)建一個(gè)線(xiàn)程去執(zhí)行這個(gè)方法:創(chuàng)建的線(xiàn)程默認(rèn)是前臺(tái)線(xiàn)程
            Thread thread = new Thread(new ThreadStart(Test));
            //Start方法標(biāo)記這個(gè)線(xiàn)程就緒了,可以隨時(shí)被執(zhí)行,具體什么時(shí)候執(zhí)行這個(gè)線(xiàn)程,由CPU決定
            //將線(xiàn)程設(shè)置為后臺(tái)線(xiàn)程
            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是由主線(xiàn)程創(chuàng)建的,thread線(xiàn)程是另外創(chuàng)建的一個(gè)線(xiàn)程,在.NET上執(zhí)行的是托管代碼,C#強(qiáng)制要求這些代碼必須是線(xiàn)程安全的,即不允許跨線(xiàn)程訪(fǎng)問(wèn)Windows窗體的控件。

解決方案:

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

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

使用上述的方法雖然可以保證程序正常運(yùn)行并實(shí)現(xiàn)應(yīng)用的功能,但是在實(shí)際的軟件開(kāi)發(fā)中,做如此設(shè)置是不安全的(不符合.NET的安全規(guī)范),在產(chǎn)品軟件的開(kāi)發(fā)中,此類(lèi)情況是不允許的。如果要在遵守.NET安全標(biāo)準(zhǔn)的前提下,實(shí)現(xiàn)從一個(gè)線(xiàn)程成功地訪(fǎng)問(wèn)另一個(gè)線(xiàn)程創(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稱(chēng)為“回調(diào)方法”,它封裝了對(duì)另一個(gè)線(xiàn)程中目標(biāo)對(duì)象(窗體控件或其他類(lèi))的操作代碼。

(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ù)為委托類(lèi)型,可見(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不在本線(xiàn)程(跨線(xiàn)程訪(fǎng)問(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上寫(xiě)出線(xiàn)程安全的代碼了。

使用方法回調(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è)線(xiàn)程去執(zhí)行這個(gè)方法:創(chuàng)建的線(xiàn)程默認(rèn)是前臺(tái)線(xiàn)程
            Thread thread = new Thread(new ThreadStart(Test));
            //Start方法標(biāo)記這個(gè)線(xiàn)程就緒了,可以隨時(shí)被執(zhí)行,具體什么時(shí)候執(zhí)行這個(gè)線(xiàn)程,由CPU決定
            //將線(xiàn)程設(shè)置為后臺(tái)線(xiàn)程
            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 終止線(xiàn)程

若想終止正在運(yùn)行的線(xià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è)異步方法,在屬性里面把輸出類(lèi)型改成控制臺(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)橹骶€(xiàn)程(即UI線(xiàn)程)在忙于計(jì)算。

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

在仔細(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)線(xiàn)程,操作系統(tǒng)收到申請(qǐng)以后,返回執(zhí)行的順序是無(wú)序的,所以啟動(dòng)是無(wú)序的。結(jié)束無(wú)序是因?yàn)殡m然線(xiàn)程執(zhí)行的是同樣的操作,但是每個(gè)線(xiàn)程的耗時(shí)是不同的,所以結(jié)束的時(shí)候不一定是先啟動(dòng)的線(xiàn)程就先結(jié)束。從上面同步方法中可以清晰的看出:btnSync_Click_0執(zhí)行時(shí)間耗時(shí)不到3秒,而btnSync_Click_1執(zhí)行時(shí)間耗時(shí)超過(guò)了3秒。可以想象體育比賽中的跑步,每位運(yùn)動(dòng)員聽(tīng)到發(fā)令槍起跑的順序不同,每位運(yùn)動(dòng)員花費(fèi)的時(shí)間不同,最終到達(dá)終點(diǎn)的順序也不同。

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

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

四、回調(diào)

先來(lái)看看異步多線(xiàn)程無(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);
      // 需求:異步多線(xiàn)程執(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é)果:

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

既然異步多線(xiàn)程是無(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ù)類(lèi)型是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é)果:

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

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

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

private void btnAsyncAdvanced_Click(object sender, EventArgs e)
{
            // 需求:異步多線(xiàn)程執(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)解決異步多線(xiàn)程無(wú)序的問(wèn)題了。

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

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

private void btnAsyncReturnVlaue_Click(object sender, EventArgs e)
{
       // 定義一個(gè)無(wú)參數(shù)、int類(lèi)型返回值的委托
       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é)果:

到此這篇關(guān)于深入學(xué)習(xí)C#多線(xiàn)程的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • c#值類(lèi)型和引用類(lèi)型使用示例

    c#值類(lèi)型和引用類(lèi)型使用示例

    這篇文章主要介紹了c#值類(lèi)型和引用類(lèi)型使用示例,需要的朋友可以參考下
    2014-04-04
  • C# 9.0新特性——只初始化設(shè)置器

    C# 9.0新特性——只初始化設(shè)置器

    這篇文章主要介紹了C# 9.0新特性——只初始化設(shè)置器的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-11-11
  • c# wpf如何更好的使用Application程序集資源

    c# wpf如何更好的使用Application程序集資源

    這篇文章主要介紹了c# wpf如何更好的使用Application程序集資源,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-04-04
  • c#二維碼生成的代碼分享

    c#二維碼生成的代碼分享

    c#生成二維碼實(shí)現(xiàn)示例代碼分享,生成方法是調(diào)用外網(wǎng)API,為了不直接調(diào)用別人的接口,創(chuàng)建一個(gè)QrImg.aspx用于顯示二維碼,傳參數(shù)即可
    2013-12-12
  • Unity實(shí)現(xiàn)手機(jī)搖一搖震動(dòng)

    Unity實(shí)現(xiàn)手機(jī)搖一搖震動(dòng)

    這篇文章主要為大家詳細(xì)介紹了untiy實(shí)現(xiàn)手機(jī)搖一搖震動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • C# Winform 子窗體訪(fǎng)問(wèn)父級(jí)窗體的控件和屬性

    C# Winform 子窗體訪(fǎng)問(wèn)父級(jí)窗體的控件和屬性

    本文主要介紹兩種子窗體訪(fǎng)問(wèn)父窗體控件和屬性的方法,大家可以參考一下,本人比較偏向第二種,把父窗體作為屬性傳遞,一勞永逸,想訪(fǎng)問(wèn)父窗體的什么控件屬性都可以。
    2016-05-05
  • C#編程自學(xué)之?dāng)?shù)據(jù)類(lèi)型和變量二

    C#編程自學(xué)之?dāng)?shù)據(jù)類(lèi)型和變量二

    這篇文章繼續(xù)介紹了C#數(shù)據(jù)類(lèi)型和變量,是對(duì)上一篇文章的補(bǔ)充,希望對(duì)大家的學(xué)習(xí)有所幫助。
    2015-10-10
  • C#獨(dú)立域名查詢(xún)代碼

    C#獨(dú)立域名查詢(xún)代碼

    C#獨(dú)立域名查詢(xún)代碼...
    2007-04-04
  • C#與PLC通訊的實(shí)現(xiàn)代碼

    C#與PLC通訊的實(shí)現(xiàn)代碼

    這篇文章主要介紹了C#與PLC通訊的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • WPF+SkiaSharp實(shí)現(xiàn)自繪投籃小游戲

    WPF+SkiaSharp實(shí)現(xiàn)自繪投籃小游戲

    這篇文章主要介紹了如何利用WPF+SkiaSharp實(shí)現(xiàn)自繪投籃小游戲。此案例主要是針對(duì)光線(xiàn)投影法碰撞檢測(cè)功能的示例,順便做成了一個(gè)小游戲,很簡(jiǎn)單,但是,效果卻很不錯(cuò),感興趣的可以動(dòng)手嘗試一下
    2022-08-08

最新評(píng)論