Unity C#基礎(chǔ)之 多線程的前世今生(上) 科普篇

多線程荸型,項(xiàng)目中的應(yīng)用不可缺少,能極大的提高程序的響應(yīng)速度乌询,但是也會(huì)提高內(nèi)存和CPU的計(jì)算量(空間換時(shí)間)背蟆,下面簡單介紹下多線程從.NET 1.0版本到.NET 4.0版本的發(fā)展歷程及使用示例

示例工程下載Unity 2017.3.0 P4 .NET版本4.6

在介紹之前先為大家科普下多線程的基礎(chǔ)知識(shí)
  • 什么是進(jìn)程
  • 什么是線程
  • 什么是多線程
  • 多線程的優(yōu)點(diǎn)
  • 多線程的缺點(diǎn)
  • 何時(shí)使用多線程
  • 何時(shí)不要使用多線程
  • 同步和異步的區(qū)別

什么是進(jìn)程?

進(jìn)程是一個(gè)程序在電腦上運(yùn)行時(shí)劣砍,全部計(jì)算資源的合集叫進(jìn)程惧蛹。當(dāng)一個(gè)程序(例如QQ)開始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源香嗓。而一個(gè)進(jìn)程又是由多個(gè)線程所組成的迅腔。(QQ信息的接受和發(fā)送,圖片接受發(fā)送靠娱,UI界面等)

什么是線程沧烈?

線程是程序的最小執(zhí)行單位,包含計(jì)算資源,,任何一個(gè)操作的響應(yīng)都是線程完成的像云。線程是程序中的一個(gè)執(zhí)行流锌雀,每個(gè)線程都有自己的專有寄存器(棧指針、程序計(jì)數(shù)器等)迅诬,但代碼區(qū)是共享的腋逆,即不同的線程可以執(zhí)行同樣的函數(shù)。(例如QQ的UI界面就是一個(gè)線程處理百框,文字的收發(fā)一個(gè)線程處理闲礼,圖片的收發(fā)一個(gè)線程處理)

什么是多線程?

多線程是指程序中包含多個(gè)執(zhí)行流铐维,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來執(zhí)行不同的任務(wù)柬泽,也就是說允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來完成各自的任務(wù)。

多線程的好處:

可以提高CPU的利用率嫁蛇。在多線程程序中锨并,一個(gè)線程必須等待的時(shí)候,CPU可以運(yùn)行其它的線程而不是等待睬棚,這樣就大大提高了程序的效率第煮。多線程特點(diǎn):不卡主線程、速度快抑党、無序性

多線程的不利方面

  • 線程也是程序包警,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多底靠;
  • 多線程需要協(xié)調(diào)和管理害晦,所以需要CPU時(shí)間跟蹤線程;
  • 線程之間對共享資源的訪問會(huì)相互影響暑中,必須解決競用共享資源的問題(線程安全)壹瘟;
  • 線程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug鳄逾;

何時(shí)使用多線程

多線程程序一般被用來在后臺(tái)執(zhí)行耗時(shí)的任務(wù)稻轨。主線程保持運(yùn)行,并且工作線程做它的后臺(tái)工作雕凹。對于Windows Forms程序來說殴俱,如果主線程試圖執(zhí)行冗長的操作政冻,鍵盤和鼠標(biāo)的操作會(huì)變的遲鈍,程序也會(huì)失去響應(yīng)粱挡。由于這個(gè)原因赠幕,應(yīng)該在工作線程中運(yùn)行一個(gè)耗時(shí)任務(wù)時(shí)添加一個(gè)工作線程,即使在主線程上有一個(gè)有好的提示“處理中...”询筏,以防止工作無法繼續(xù)榕堰。這就避免了程序出現(xiàn)由操作系統(tǒng)提示的“沒有相應(yīng)”,來誘使用戶強(qiáng)制結(jié)束程序的進(jìn)程而導(dǎo)致錯(cuò)誤嫌套。模式對話框還允許實(shí)現(xiàn)“取消”功能逆屡,允許繼續(xù)接收事件,而實(shí)際的任務(wù)已被工作線程完成踱讨。BackgroundWorker恰好可以輔助完成這一功能魏蔗。
在沒有用戶界面的程序里,比如說Windows Service痹筛, 多線程在當(dāng)一個(gè)任務(wù)有潛在的耗時(shí)莺治,因?yàn)樗诘却砼_(tái)電腦的響應(yīng)(比如一個(gè)應(yīng)用服務(wù)器,數(shù)據(jù)庫服務(wù)器帚稠,或者一個(gè)客戶端)的實(shí)現(xiàn)特別有意義谣旁。用工作線程完成任務(wù)意味著主線程可以立即做其它的事情。
另一個(gè)多線程的用途是在方法中完成一個(gè)復(fù)雜的計(jì)算工作滋早。這個(gè)方法會(huì)在多核的電腦上運(yùn)行的更快榄审,如果工作量被多個(gè)線程分開的話(使用Environment.ProcessorCount屬性來偵測處理芯片的數(shù)量)。
一個(gè)C#程序稱為多線程的可以通過2種方式:明確地創(chuàng)建和運(yùn)行多線程杆麸,或者使用.NET framework的暗中使用了多線程的特性——比如BackgroundWorker類, 線程池搁进,threading timer,遠(yuǎn)程服務(wù)器昔头,或Web Services或ASP.NET程序饼问。在后面的情況,人們別無選擇揭斧,必須使用多線程匆瓜;一個(gè)單線程的ASP.NET web server不是太酷,即使有這樣的事情未蝌;幸運(yùn)的是,應(yīng)用服務(wù)器中多線程是相當(dāng)普遍的茧妒;唯一值得關(guān)心的是提供適當(dāng)鎖機(jī)制的靜態(tài)變量問題萧吠。

何時(shí)不要使用多線程

多線程也同樣會(huì)帶來缺點(diǎn),最大的問題是它使程序變的過于復(fù)雜桐筏,擁有多線程本身并不復(fù)雜纸型,復(fù)雜是的線程的交互作用,這帶來了無論是否交互是否是有意的,都會(huì)帶來較長的開發(fā)周期还惠,以及帶來間歇性和非重復(fù)性的bugs泞莉。因此绊困,要么多線程的交互設(shè)計(jì)簡單一些,要么就根本不使用多線程瑰枫。除非你有強(qiáng)烈的重寫和調(diào)試欲望。
當(dāng)用戶頻繁地分配和切換線程時(shí)丹莲,多線程會(huì)帶來增加資源和CPU的開銷光坝。在某些情況下,太多的I/O操作是非常棘手的甥材,當(dāng)只有一個(gè)或兩個(gè)工作線程要比有眾多的線程在相同時(shí)間執(zhí)行任務(wù)快的多盯另。稍后我們將實(shí)現(xiàn)生產(chǎn)者/耗費(fèi)者 隊(duì)列,它提供了上述功能洲赵。

同步和異步的區(qū)別

同步方法調(diào)用在程序繼續(xù)執(zhí)行之前需要等待同步方法執(zhí)行完畢返回結(jié)果(同一時(shí)間只能做一件事)
異步方法則在被調(diào)用之后立即返回以便程序在被調(diào)用方法完成其任務(wù)的同時(shí)執(zhí)行其它操作(同一時(shí)間內(nèi)可以并行做多件事)

話不多說鸳惯,上Code,簡單的異步多線程

    /// <summary>
    /// 異步方法
    /// </summary>
    private void AsyncOnClick()
    {
        Debug.Log($"AsyncOnClick - Start 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");

        Action<string> act = this.DoSomethingLong;        //act("同步菜鳥海瀾");//寫法等同于act.Invoke("菜鳥海瀾");

        act.BeginInvoke("異步菜鳥海瀾", null, null);

        Debug.Log($"AsyncOnClick - End 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }
    /// <summary>
    /// 同步方法
    /// </summary>
    private void SyncOnClick()
    {
        Debug.Log($"SyncOnClick - Start 線程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        this.DoSomethingLong("同步菜鳥海瀾");
        Debug.Log($"SyncOnClick - End  線程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn):  {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }


打印結(jié)果如下

調(diào)用順序是先調(diào)用的異步然后是同步叠萍,但是打印結(jié)果是先出現(xiàn)的同步結(jié)果芝发,說明異步?jīng)]有卡主線程

異步多線程是無序的:啟動(dòng)無序 執(zhí)行時(shí)間不確定 結(jié)束無序,但是要控制他的執(zhí)行順序一般分為三類 回調(diào) 等待 狀態(tài)

回調(diào)

     /// <summary>
    /// 異步回調(diào)  
    /// </summary>
    private void AsyncCallback()
    {
        #region 無返回值
        {
            Action<string> act = this.DoSomethingLong;
            IAsyncResult iAsyncResult = null;
            AsyncCallback callback = ar =>
            {
                Debug.Log(object.ReferenceEquals(ar, iAsyncResult));
                Debug.Log(ar.AsyncState);
                Debug.Log($"這里是BeginInvoke的回調(diào){Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            };
            iAsyncResult = act.BeginInvoke("AsyncOnClick01", callback, "菜鳥海瀾");
        }
        #endregion
        #region 有返回值
        {
            Func<int, string> func = i => i.ToString();
            //AsyncCallback
            IAsyncResult iAsyncResult = func.BeginInvoke(DateTime.Now.Year, ar =>
            {
                string resultIn = func.EndInvoke(ar);//對于每個(gè)異步操作俭令,只能調(diào)用一次 EndInvoke后德。調(diào)用EndInvoke后立即強(qiáng)制釋放線程,否則系統(tǒng)在合適時(shí)機(jī)釋放線程
                Debug.Log($"This is {ar.AsyncState} 的異步調(diào)用結(jié)果 {resultIn}");
            }, "回調(diào)傳參菜鳥海瀾");
            //string result = func.EndInvoke(iAsyncResult);//獲取返回值 不在回調(diào)中調(diào)用EndInvoke卡主線程
        }
        #endregion
    }

打印結(jié)果如下

等待

    /// <summary>
    /// 異步等待
    /// </summary>
    private void AsyncWaitOnClick()
    {
        {
            Action<string> act = this.DoSomethingLong;
            IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);

            int i = 1;
            while (!iAsyncResult.IsCompleted)//1 卡界面抄腔,主線程在等待   2 邊等待邊做事兒  3有誤差
            {
                if (i < 10)
                {
                    Debug.Log($"文件上傳{i++ * 10}%瓢湃。。赫蛇。請等待");
                }
                else
                {
                    Debug.Log("已完成99%绵患。。悟耘。馬上結(jié)束");
                }
                Thread.Sleep(200);
            }
            Debug.Log("文件上傳成功B潋!暂幼!");

            act.EndInvoke(iAsyncResult);//等待
            Debug.Log("這里是BeginInvoke調(diào)用完成之后才執(zhí)行的筏勒。。旺嬉。");
        }
    }

打印結(jié)果如下

狀態(tài)

    /// <summary>
    /// 異步狀態(tài)
    /// </summary>
    private void AsyncStateOnClick()
    {
        Debug.Log($"AsyncStateOnClick Start {name}  線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
        Action<string> act = this.DoSomethingLong;
        IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
        //iAsyncResult.AsyncWaitHandle.WaitOne();//一直等待任務(wù)完成管行,第一時(shí)間進(jìn)入下一行
        //iAsyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任務(wù)完成,第一時(shí)間進(jìn)入下一行
        iAsyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms邪媳,否則就進(jìn)入下一行捐顷,可以做一些超時(shí)控制
        Debug.Log($"AsyncStateOnClick End {name}  線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    }

打印結(jié)果如下

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荡陷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迅涮,更是在濱河造成了極大的恐慌废赞,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叮姑,死亡現(xiàn)場離奇詭異唉地,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戏溺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門渣蜗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旷祸,你說我怎么就攤上這事耕拷。” “怎么了托享?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵骚烧,是天一觀的道長。 經(jīng)常有香客問我闰围,道長赃绊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任羡榴,我火速辦了婚禮碧查,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘校仑。我一直安慰自己忠售,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布迄沫。 她就那樣靜靜地躺著稻扬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪羊瘩。 梳的紋絲不亂的頭發(fā)上泰佳,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音尘吗,去河邊找鬼逝她。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睬捶,可吹牛的內(nèi)容都是我干的黔宛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼侧戴,長吁一口氣:“原來是場噩夢啊……” “哼宁昭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酗宋,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤积仗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蜕猫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寂曹,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年回右,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隆圆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翔烁,死狀恐怖渺氧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹬屹,我是刑警寧澤侣背,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站慨默,受9級(jí)特大地震影響贩耐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜厦取,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一潮太、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虾攻,春花似錦铡买、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朋沮,卻和暖如春蛇券,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背樊拓。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工纠亚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筋夏。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓蒂胞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親条篷。 傳聞我的和親對象是個(gè)殘疾皇子骗随,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 米斯特白帽培訓(xùn)講義 工具篇 AWVS 講師:gh0stkey 整理:飛龍 協(xié)議:CC BY-NC-SA 4.0 功...
    布客飛龍閱讀 1,501評(píng)論 0 8
  • 我討厭現(xiàn)在的自己蛤织。自從經(jīng)歷過一些事情之后,就變得頹廢鸿染,每天很迷茫指蚜。 作為一個(gè)寶媽涨椒,我知道我這樣是朝黃臉...
    琪琪媽咪閱讀 410評(píng)論 2 3
  • 今天老師給我們大概講解了兩個(gè)小模塊摊鸡,溫濕度傳感器和語音模塊,我做的是溫濕度傳感器蚕冬,通信通過IIC來讀取溫濕度傳感...
    葛書雨g閱讀 176評(píng)論 0 0
  • ? 還有不到 1 個(gè)月就要迎來了一年一度的春節(jié)小長假躺屁。雖然很多小仙女已經(jīng)按耐不住激動(dòng)的小心情肯夏,然而2月6號(hào)快遞陸續(xù)...
    彩妝國季閱讀 272評(píng)論 0 0
  • 【作 業(yè)】 作業(yè)一:A3-選其一,用說話三步驟來進(jìn)行案例練習(xí): 1犀暑、職場案例: 你最近在工作上遇到了一些困惑驯击,你...
    gxuzh閱讀 479評(píng)論 0 49