Unity中關于Coroutine與Async的使用問題

Coroutine(協(xié)程)我想大家都很熟悉了滓侍,由于Unity是單線程的引擎拜姿,我們在做一些異步操作的時候都是靠著協(xié)程來辦到的朽砰。然而,隨著Unity更新到2017版本及以上的版本鹿寨,Runtime可以支持到.NET 4.x Equivalent時,C#中的異步操作就可以使用Thread的升級版Task以及async薪夕、await這些東西了脚草。

我們先來看一個很普通場景,從網(wǎng)上獲取一些json數(shù)據(jù)到本地(這個例子里我都從https://jsonplaceholder.typicode.com/users獲取數(shù)據(jù))原献,以便做進一步處理馏慨,通常我們會開一個協(xié)程,比如這樣

IEnumerator FetchData(){
        Users[] users;
        
        // USERS
        UnityWebRequest www = UnityWebRequest.Get(USERS_URL);
        yield return www.SendWebRequest();
        if (www.isHttpError || www.isNetworkError)
        {
            Debug.Log("A network error occurred");
            yield break;
        }
        string json = www.downloadHandler.text;
        try
        {
            users = JsonHelper.GetJsonArray<Users>(json);
            
        }
        catch
        {
            Debug.Log("An error occurred");
            yield break;
        }
        
        // OUTPUT
        foreach (Users user in users)
        {
            Debug.Log(user.name);
        }
        
    }

然后在需要的時候調(diào)用

void Update()
    {
        if(Input.GetKeyDown(KeyCode.Space)){
            StartCoroutine(FetchData());
        }
        
    }

這樣寫代碼大概算是天經(jīng)地義了姑隅。然而写隶,Coroutine也有它自己的不足。首先粤策,它無法返回值樟澜,或者說需要通過一些比較復雜的方法才能使它能夠返回值,這樣我們不得不寫一個很長的單體式協(xié)程(monolithic coroutine叮盘,大概是這么翻譯的吧秩贰,我也不清楚);其次柔吼,yield無法放入try catch中毒费,我們不得不創(chuàng)建一個混合了同步(try catch)與異步(www.isHttpErrorwww.isNetworkError)的錯誤處理機制。

所以愈魏,在當下的Unity版本中觅玻,有時候我們能用異步編程來代替協(xié)程,以此來規(guī)避一些協(xié)程所帶來的困擾培漏。想要使用異步溪厘,首先要確保Runtime版本,在Unity2017及以上的版本中牌柄,點擊Edit > Project Settings > Player > Configuration > Scripting Runtime Version > .NET 4.x Equivalent畸悬。如果你在使用2017以前的Unity版本,很遺憾珊佣,這個新功能你無法使用了o(╥﹏╥)o

那么現(xiàn)在我們來改寫之前的協(xié)程蹋宦,將它變成異步

async Task<Users[]> FetchUsers(){
        UnityWebRequest www = UnityWebRequest.Get(USERS_URL);
        www.SendWebRequest();
        while (!www.isDone){
            await Task.Delay(100);
        }
        if (www.isHttpError || www.isNetworkError)
        {
            throw new System.Exception();
        }
        string json = www.downloadHandler.text;        
        Users[] res = JsonHelper.GetJsonArray<Users>(json);
        return res;
        
    }

然后是調(diào)用

async void LateUpdate() {//這里的方法不標記為async則下面會報錯
        if(Input.GetKeyDown(KeyCode.L)){
            try
            {
                //方法不標記為async則報錯:The 'await' operator can only be used within 
                //an async method. Consider marking this method with the 'async' modifier 
                //and changing its return type to 'Task'.
                Users[] users = await FetchUsers();
                for (int i = 0; i < users.Length; i++)
                {
                    Debug.Log(users[i].name);
                }
            }
            catch (System.Exception)
            {
                Debug.Log("An error occurred");
            }
            
        }
    }

這里要注意幾點:

  1. 方法用async標記后,如果方法內(nèi)沒有出現(xiàn)await咒锻,那么這個方法的調(diào)用和普通方法的調(diào)用沒有區(qū)別冷冗。
  2. 有await時,在await之前的代碼依然在主線程內(nèi)按順序執(zhí)行惑艇,直到遇到await才線程阻塞蒿辙。
  3. await可以理解為等待方法執(zhí)行完成,除了標記async外,還能標記Task须板,表示等待該線程完成碰镜。所以await并不是針對async方法,而是針對async方法返回給我們的Task习瑰。
  4. async只能標記返回型為void、Task或者Task<T>的方法秽荤。

由于我也是第一次用C#的異步甜奄,所有的一切對我來說也很新,如果有希望了解更多的小伙伴們窃款,可以去官方看相關文檔
Asynchronous programming with async and await
async (C# Reference)
await operator (C# reference)

最后课兄,我本來以為這個東西可以替代Coroutine的另一個原因是Coroutine有gc,網(wǎng)上一查有關協(xié)程gc的博客一大堆晨继。然而烟阐,我親自試驗了一遍,寫了個最簡單的協(xié)程來測試gc情況紊扬,發(fā)現(xiàn)協(xié)程的gc問題是蜒茄。。餐屎。根本沒有問題L锤稹!腹缩!

private static WaitForSecondsRealtime w = new WaitForSecondsRealtime(1);

IEnumerator Counter(){
        for (int i = 0; i < 100000; i++)
        {
            // Debug.Log(i);
            yield return null;
            // yield return 0;
            // yield return w;
            // yield return new WaitForSeconds(1);
        }
    }

以上是我試驗的好幾種情況屿聋,返回null、返回一個數(shù)字藏鹊、返回一個new WaitForSeconds(1)润讥,將new WaitForSecondsRealtime(1)作為全局變量使用,都試了一遍后盘寡,發(fā)現(xiàn)只有在return 0的情況下才會發(fā)生gc楚殿,其余情況都不會有gc發(fā)生

return 0的情況有gc

而我為了知道協(xié)程被開啟了所以在協(xié)程里用了Debug.Log()宴抚,這個東西才是gc大戶勒魔,直接產(chǎn)生了6.1kb的gc,我去9角9诰睢!

Log產(chǎn)生的gc

而閱讀Unity的日志可以知道常潮,在Unity 5.3.6以前弟胀,協(xié)程的gc問題的確存在,但從Unity 5.3.6開始,這個問題已經(jīng)被修復了孵户!

所以萧朝,Unity原生協(xié)程可以放心使用,沒有gc問題O目蕖<旒怼!真正有問題的是Debug.Log()竖配,這東西在線上包內(nèi)不要存在何址,嚴重影響性能!=琛用爪!
經(jīng)網(wǎng)友提醒,以上結(jié)論有誤胁镐,頻繁調(diào)用startCoroutine會產(chǎn)生gc(我的例子是在一個協(xié)程里不斷的yield偎血,沒有gc),原生的協(xié)程真的有點問題盯漂,建議自己寫一套或者unity asset store下一個高效協(xié)程插件使用颇玷,不過呢,頻繁調(diào)用(比如說每幀)startCoroutine真的合理么宠能?

在Update里每幀調(diào)用startCoroutine亚隙,gc是肯定有的

至于為什么return 0會有gc,那是因為return 0發(fā)生了裝箱拆箱操作违崇,不可避免的產(chǎn)生了gc阿弃,StackOverFlow上有人回答了這個問題,地址在https://stackoverflow.com/questions/39268753/what-is-the-difference-between-yield-return-0-and-yield-return-null-in-corou羞延,所以不要認為return 0return null是一樣的渣淳,都是等一幀。其實伴箩,他們不一樣入愧,任何時候都推薦使用return null來等一幀。

完整示例在項目地址嗤谚,打開Coroutine場景即可棺蛛。

參考
Unity3D里foreach,using和Coroutine的GC問題探究及解決方案
Unity: Leveling up with Async / Await / Tasks
C#基礎系列——異步編程初探:async和await

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巩步,隨后出現(xiàn)的幾起案子旁赊,更是在濱河造成了極大的恐慌,老刑警劉巖椅野,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件终畅,死亡現(xiàn)場離奇詭異籍胯,居然都是意外死亡,警方通過查閱死者的電腦和手機离福,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門杖狼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妖爷,你說我怎么就攤上這事蝶涩。” “怎么了絮识?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵子寓,是天一觀的道長。 經(jīng)常有香客問我笋除,道長,這世上最難降的妖魔是什么炸裆? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任垃它,我火速辦了婚禮,結(jié)果婚禮上烹看,老公的妹妹穿的比我還像新娘国拇。我一直安慰自己,他們只是感情好惯殊,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布酱吝。 她就那樣靜靜地躺著,像睡著了一般土思。 火紅的嫁衣襯著肌膚如雪务热。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天己儒,我揣著相機與錄音崎岂,去河邊找鬼。 笑死闪湾,一個胖子當著我的面吹牛冲甘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播途样,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼江醇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了何暇?” 一聲冷哼從身側(cè)響起陶夜,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赖晶,沒想到半個月后律适,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辐烂,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年捂贿,在試婚紗的時候發(fā)現(xiàn)自己被綠了纠修。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡厂僧,死狀恐怖扣草,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颜屠,我是刑警寧澤辰妙,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站甫窟,受9級特大地震影響密浑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粗井,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一尔破、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浇衬,春花似錦懒构、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至醉冤,卻和暖如春秩霍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冤灾。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工前域, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人韵吨。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓匿垄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親归粉。 傳聞我的和親對象是個殘疾皇子椿疗,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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