You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity AR应用蓝牙GPS读取卡顿及线程锁异常问题求助

问题:Unity AR应用蓝牙GPS读取卡顿与线程锁崩溃问题

我开发了一款使用ARLocation插件的Unity AR应用,使用设备自身GPS时运行正常,但连接发送高精度定位的蓝牙设备后,相机出现卡顿,卡顿恰好发生在ReadLine读取蓝牙数据的代码行。定位需要每帧更新,推测可能是蓝牙设备数据发送频率不足。我尝试将读取逻辑放入后台线程并使用锁,却触发了“Other thread trapped, signum = 6”错误,多次调整锁的实现仍未解决。


相关代码片段

1. Update帧更新逻辑

public virtual void Update()
{
    if (!HasStarted)
    {
        return;
    }

    var location = ReadLocation();
    var heading = ReadHeading();

    if (location == null || heading == null)
    {
        // Debug.Log("[AbstractLocationProvider]: Null reading");
        return;
    }

    if (FirstReading)
    {
        FirstLocation = location.Value;
        CurrentLocation = FirstLocation;
        CurrentLocationRaw = FirstLocation;
        CurrentHeading = heading.Value;

        IsEnabled = true;
        FirstReading = false;

        LocationEnabled?.Invoke();
        InnerOnEnabled();

        EmitCompassUpdated();
        EmitLocationUpdated();
        EmitLocationUpdatedRaw();

        return;
    }

    UpdateLocation(location.Value);
    UpdateHeading(heading.Value);
}

2. GPS数据读取解析逻辑

protected override LocationReading? ReadLocation()
{
    if (!HasStarted)
    {
        return null;
    }

    if (Base.blAdapter.reader != null)
    {
        lock (Base.locker)
        {
            double längenString = Double.Parse(Base.lastGGAMessage.Split(",")[2].Replace(".", ",")) / 100; //I read the gps string here
            

            double grad = Math.Floor(längenString);
            double minuten = längenString - grad;
            double dezimalminuten = minuten / 0.6;

            double lat = dezimalminuten + grad;

            längenString = Double.Parse(Base.lastGGAMessage.Split(",")[4].Replace(".", ",")) / 100;

            grad = Math.Floor(längenString);
            minuten = längenString - grad;
            dezimalminuten = minuten / 0.6;

            double lng = dezimalminuten + grad;

            return new LocationReading()
            {
                latitude = lat,
                longitude = lng,
                altitude = double.Parse(Base.lastGGAMessage.Split(",")[9].Replace(".", ",")),
                accuracy = Base.HRMS,
                floor = -1,
                timestamp = (long)(Time.time * 1000)
            };
        }
    }
    else
    {
        var data = Input.location.lastData;
        return new LocationReading()
        {
            latitude = data.latitude,
            longitude = data.longitude,
            altitude = data.altitude,
            accuracy = data.horizontalAccuracy,
            floor = -1,
            timestamp = (long)(Time.time * 1000)
        };
    }
}

3. 蓝牙后台线程读取逻辑

public static void ReadMessageToEnd(System.Object o)
{
    while (Base.isConnected)
    {
        string line = null;

        try
        {
            line = Base.blAdapter.reader.ReadLine();
        }
        catch (Exception)
        {
            return;
        }

        if (line.Length > 0)
        {
            switch (line.Split(",")[0])
            {
                case "$GPGGA":
                    lock (Base.locker)
                    {
                        Base.lastGGAMessage = line; //this is the important line
                    }

                    if (line.Split(",")[6].Equals("0"))
                    {
                        Base.invalidFix = true;
                    }
                    else
                    {
                        Base.invalidFix = false;
                    }

                    break;

                case "@GEPOSE":
                    Base.gNeigungData = line;
                    break;

                case "$GPGST":

                    try
                    {
                        Base.VRMS = Math.Round(double.Parse(line.Split(",")[8].Replace(".", ",")), 2);
                        Base.HRMS = Math.Round(Math.Sqrt(Math.Pow(double.Parse(line.Split(",")[6].Replace(".", ",")), 2) + Math.Pow(double.Parse(line.Split(",")[7].Replace(".", ",")), 2)), 2);

                    }
                    catch (Exception)
                    {
                    }

                    break;

                default:
                    break;
            }
        }
    }
}

Unity LogCat崩溃日志

2023.02.06 14:42:07.596 20304 21788 Error CRASH other thread is trapped; signum = 6
0001.01.01 00:00:00.000 -1 -1 Info  --------- beginning of crash
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime FATAL EXCEPTION: UnityMain
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime Process: com.ld2.LD2Survey, PID: 20304
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime java.lang.Error: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime Version '2021.3.17f1 (3e8111cac19d)', Build type 'Development', Scripting Backend 'il2cpp', CPU 'arm64-v8a'
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime Build fingerprint: 'realme/RMX2075EEA/RMX2075L1:12/RKQ1.211103.002/Q.GDPR.202211032034:user/release-keys'
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime Revision: '0'
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime ABI: 'arm64'
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime Timestamp: 2023-02-06 14:42:07+0100
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime pid: 20304, tid: 22085, name: Thread-58  >>> com.ld2.LD2Survey <<<
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime uid: 10544
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x0  0000000000000000  x1  0000000000005645  x2  0000000000000006  x3  0000007a2d184e60
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x4  fefefefefefefeff  x5  fefefefefefefeff  x6  fefefefefefefeff  x7  7f7f7f7f7f7f7f7f
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x8  00000000000000f0  x9  c77d01e1ee66ea5d  x10 0000000000000000  x11 ffffff80fffffbdf
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x12 0000000000000001  x13 00000000000000de  x14 0000007a2d183c60  x15 ffffffffffffffff
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x16 0000007d135d0bc0  x17 0000007d135ab680  x18 0000007a2c680000  x19 0000000000004f50
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x20 0000000000005645  x21 00000000ffffffff  x22 0000007c80815000  x23 0000000000000000
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x24 0000007a2d185038  x25 0000007a2d188000  x26 0000007a2d188000  x27 0000000000000001
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     x28 000000000000004c  x29 0000007a2d184ee0
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime     sp  0000007a2d184e40  lr  0000007d1355b99c  pc  0000007d1355b9c8
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime 
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime backtrace:
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime       #00 pc 00000000000799c8  /apex/com.android.runtime/lib64/bionic/libc.so (abort+168) (BuildId: bbbdeb7c87c74f1491f92c6e605095b0)
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime 
2023.02.06 14:42:07.597 20304 21788 Error AndroidRuntime    at libc.abort(abort:168)

问题分析与修复方案

1. 缩小锁范围,避免主线程长时间持有锁

当前ReadLocation中把整个GPS解析逻辑都包裹在锁内,导致主线程(Update调用)长时间占用锁,后台线程无法及时更新共享变量,甚至引发死锁。仅在读写共享变量时加锁,解析逻辑放到锁外:

protected override LocationReading? ReadLocation()
{
    if (!HasStarted)
    {
        return null;
    }

    if (Base.blAdapter.reader != null)
    {
        string ggaMessage = null;
        double currentHRMS = Base.HRMS;
        // 仅读取共享变量时加锁,缩小锁持有时间
        lock (Base.locker)
        {
            ggaMessage = Base.lastGGAMessage;
            currentHRMS = Base.HRMS;
        }

        if(string.IsNullOrEmpty(ggaMessage)) return null;

        try
        {
            var split = ggaMessage.Split(",");
            double längenString = Double.Parse(split[2].Replace(".", ",")) / 100;

            double grad = Math.Floor(längenString);
            double minuten = längenString - grad;
            double dezimalminuten = minuten / 0.6;
            double lat = dezimalminuten + grad;

            längenString = Double.Parse(split[4].Replace(".", ",")) / 100;
            grad = Math.Floor(längenString);
            minuten = längenString - grad;
            dezimalminuten = minuten / 0.6;
            double lng = dezimalminuten + grad;

            return new LocationReading()
            {
                latitude = lat,
                longitude = lng,
                altitude = double.Parse(split[9].Replace(".", ",")),
                accuracy = currentHRMS,
                floor = -1,
                timestamp = (long)(Time.time * 1000)
            };
        }
        catch(Exception ex)
        {
            Debug.LogError($"GPS数据解析失败: {ex.Message}");
            return null;
        }
    }
    else
    {
        var data = Input.location.lastData;
        return new LocationReading()
        {
            latitude = data.latitude,
            longitude = data.longitude,
            altitude = data.altitude,
            accuracy = data.horizontalAccuracy,
            floor = -1,
            timestamp = (long)(Time.time * 1000)
        };
    }
}

2. 完善后台线程的线程安全与异常处理

后台线程中所有共享变量的读写都需要加锁,同时补充异常处理避免线程意外退出:

public static void ReadMessageToEnd(System.Object o)
{
    while (Base.isConnected)
    {
        string line = null;
        try
        {
            line = Base.blAdapter.reader.ReadLine();
        }
        catch (IOException ex)
        {
            Debug.LogError($"蓝牙读取失败: {ex.Message}");
            break;
        }
        catch (Exception ex)
        {
            Debug.LogError($"未知读取错误: {ex.Message}");
            continue;
        }

        if (string.IsNullOrEmpty(line)) continue;

        try
        {
            var split = line.Split(",");
            if(split.Length == 0) continue;
            
            switch (split[0])
            {
                case "$GPGGA":
                    lock (Base.locker)
                    {
                        Base.lastGGAMessage = line;
                    }
                    Base.invalidFix = split[6].Equals("0");
                    break;

                case "@GEPOSE":
                    lock(Base.locker)
                    {
                        Base.gNeigungData = line;
                    }
                    break;

                case "$GPGST":
                    if(split.Length <9) break;
                    try
                    {
                        double vrms = Math.Round(double.Parse(split[8].Replace(".", ",")), 2);
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 23:55:18