在Unity中檢測死循環和卡死


當游戲在手機/模擬器上卡死,logcat沒有日志輸出,也沒有卡死堆棧信息或者bugly也沒有捕獲到異常,你是否很焦急?本文介紹一下我們項目中檢測Unity卡死的方法,也許適合你使用。

實現原理

在絕大多數情況下我們可以認為Unity是單線程的,基於這點我們在Unity的系統函數FixedUpdate中統計游戲運行期間的總幀數,如果Unity沒有卡死,那么TotalFrame是會一直累加的,如果在某一段時間內TotalFrame都不會變化了,則可以認為Unity已經卡死了

既然Unity的主線程已經卡死了,我們就需要用另一個線程用來定時檢查unity主線程中的TotalFrame是否不會變化了

示例代碼

using System;
using System.Threading;
using UnityEngine;

namespace KEngine
{
    /// <summary>
    /// 開另外一個線程檢測unity是否被卡死
    /// </summary>
    public static class UnityThreadDetect
    {
        public static Thread _MainThread = System.Threading.Thread.CurrentThread;//獲取unity線程
        private static int check_interval = 3000;//檢測間隔

        public static void Start()
        {
            new Thread(CheckMainThread).Start();
        }
        
        static void CheckMainThread()
        {
            long frame = 0;
            while(!AppEngine.IsApplicationQuit)
            {
                frame = AppEngine.TotalFrame;
                Thread.Sleep(check_interval);
                if (frame == AppEngine.TotalFrame)
                {
                    Log.LogToFile("unity thread dead,ThreadState:{0}",_MainThread.ThreadState);
                    if (AppEngine.IsApplicationFocus)
                    {
                        //todo report error
                    }
                }
            }
        }
    }
}

捕獲卡死的方法名

在我們的游戲中一般出現卡死的情況都是在定時器里面,我們的定時器是通過在Unity的Update驅動定時器列表,當卡死時,在另一個線程中打印出定時器中正在執行的函數就可以定位到卡死的函數了。定時器可參考:UnityTimer中的Timer.cs

同時在Unity的Update進行派發多個事件,比如PreUpdate,Update,以便出問題更容易定位到卡在那兒

舉例說明問題

下面舉例我們遇到的出現卡死的問題

死循環

下面這個死循環在Unity中會卡死,而在.NET中不會,.NET中當i超過byte的最大值255時i會從0開始

public static void TesBadCode()
{
	byte i = 0;  
	while (true)
	{
		i++;
	}
}

目前我們遇到的絕大多數情況都是邏輯代碼中寫了where(true) do xxx 然后里面某些情況不會break,導致循環永遠退不出來

屏蔽了事件系統

在某些系統中屏蔽掉了UGUI的事件系統,導致無法接受用戶輸入,這個問題不應該歸類為Unity卡死,但用戶反饋來看就是卡死了,無法操作。

重復添加定時器

起因是底層沒有對同名定時器進行限制,在某些邏輯中誤使用,出現每秒添加一個定時器,而定時器中的邏輯很大且長時間不退出的,當不斷添加重復定時器就導致游戲運行越來越慢

重復注冊事件

在一些界面的刷新函數和控制器函數,被頻繁重復注冊了事件,導致在拋出事件時,同一個函數被調用了N次,這個問題在Unity的Profiler中可以清晰看到函數的調用次數

擴展

遞歸調用

遞歸調用,會報stack overflow,不會讓unity卡死

為什么無限循環遞歸調用不會卡死Unity?

這是因為每個方法的方法調用棧容量是有限的,當超出之后就會跳出報stack overflow,不會讓應用程序卡死

public static void TesBadCode()
{
	while (true)
	{
		TesBadCode();
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM