Приложение Unity для HoloLens потенциально перегружено с помощью сокетов, но не получает никаких исключений?

В настоящее время я работаю над исследовательским проектом, который включает получение данных в реальном времени (до 10 циклов в секунду) с внешнего сканера и их отображение на HoloLens 2 с помощью Unity. В настоящее время система разделена на две части:

  • Сервер Python, который отправляет пакет с данными от внешнего сканера через сокет TCP
  • Клиент HoloLens, который принимает обработанные данные пакета и отображает их пользователю.

Часть HoloLens построена как клиент асинхронного сокета, и как только он получит пакет данных, он соответствующим образом обновит некоторые компоненты пользовательского интерфейса и GameObject. Все данные, которые я отправляю, представляют собой простые строки JSON, которые используют мою собственную структуру пакета, и мне удалось успешно получить данные сканера через сокет с помощью HoloLens, но кажется, что как только сканер начинает отправлять слишком много data HoloLens перестает принимать новые пакеты и прекращает обработку чего-либо.

Это странно, так как тесты запуска того же приложения в редакторе Unity на моем компьютере не показывают этих проблем. Я понимаю, что это условие гонки/бесконечного опроса, но в настоящее время я использую C# Socket.BeginReceive() API для асинхронного ожидания новых данных, и, насколько я понимаю документацию MSDN, это должно обрабатывать любые входящие данные с отдельными потоками. Я не понимаю, откуда на HoloLens возникает проблема, так как из журналов отладки не выбрасываются исключения, и система работает с меньшими размерами данных (ближе к 2 циклам в секунду). Другая идея может заключаться в том, что буфер для сокета может содержать> 1 пакет и не может правильно проанализировать, но не вызовет ли это исключение Newtonsoft.Json?

Вот фрагмент моего клиента HoloLens:

    public class Manager : MonoBehaviour
    {
        public string serverSocketEndpoint = "localhost";
        public string serverCommandEndpoint = "localhost:5000/command";

        private Dictionary<string, PacketCategoryData> _tagCounts;

        private bool _connected = false;
        private static Socket _socket;
        private byte[] _socketBuffer = new byte[8192];

        private Thread _socketThread;
        
        void Start()
        {
            StartSocketConnection();
        }


        public void StartSocketConnection() {
            try
            {
                IPAddress ipAddr = Dns.GetHostAddresses(serverSocketEndpoint)[1];
            
                IPEndPoint writeEndPoint = new IPEndPoint(ipAddr, 12345);
                IPEndPoint readEndPoint = new IPEndPoint(ipAddr, 12346);
            
                _socket = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                _socket.NoDelay = true;
                _socket.ReceiveBufferSize = 8192;
                _socket.Connect(writeEndPoint);
                
                Debug.Log(String.Format("Socket connected to {0} ", _socket.RemoteEndPoint.ToString()));
                
                _socket.BeginReceive(_socketBuffer, 0, _socketBuffer.Length, SocketFlags.None, OnDataReceived, _socket);
            }
            
            catch (Exception e)
            {
                Debug.Log(e.ToString());
                debugMenu.UpdateStats();
            } 
        }
        

        private void OnDataReceived(IAsyncResult result)
        {
            Socket connection = result.AsyncState as Socket;
            int received = connection.EndReceive(result);

            Debug.Log("Got packet data");
            
            // Handle received data in buffer.
            string incomingData = Encoding.ASCII.GetString(_socketBuffer, 0, received);
            DataPacket packet = JsonConvert.DeserializeObject<DataPacket>(incomingData);
            
            // Start a new async receive on the client to receive more data.
            _socket.BeginReceive(
                _socketBuffer, 
                0,
                _socketBuffer.Length,
                SocketFlags.None,
                OnDataReceived,
                _socket);
            
            // Parse the previous packet that we picked up
            ParseIncomingPacket(packet);
        }
        
        
        public void ParseIncomingPacket(DataPacket packet)
        {
            try
            {
                if (packet != null)
                {
                    if (packet.type.Equals("count_update"))
                    {
                        // Do some data processing and UI updating on the main thread...
                        // This particular place is where things go wrong, or atleast this general area
                        // Until I surpass the threshold of about 2 or 3 cycles a second, everything is
                        // being processed correctly, but as soon as I surpass that limit everything just stops
                        // with no errors

                        UnityMainThreadDispatcher.Instance().Enqueue(() =>
                        {
                            Logger.Log("[Info] Got count_update response");
                        });
                        
                        _connected = true;
                        
                        var json = JsonConvert.SerializeObject(packet.data);
                        Dictionary<string, PacketCategoryData> categoryCounts = 
                            JsonConvert.DeserializeObject<Dictionary<string, PacketCategoryData>>(json);

                        // Since this is Unity game object specific code, it must be ran on the main thread
                        UnityMainThreadDispatcher.Instance().Enqueue(CountDataCallback());
                    }
                }
                else
                {
                    //
                    // Exception conditions...
                    //
                }
            }
            
            catch (Exception e)
            {
                UnityMainThreadDispatcher.Instance().Enqueue(() =>
                {
                    Debug.LogError(String.Format("Unexpected Exception : {0}", e.ToString()));
                    _connected = false;
                    debugMenu.UpdateStats();
                });
            }
        }


        private IEnumerator CountDataCallback() {
            UpdateTagCountUI();
            debugMenu.UpdateStats();
            yield return null;
        }
    }

Любая помощь будет принята с благодарностью.

Редактировать: Я использую внешнюю библиотеку для UnityMainThreadDispatcher, поскольку BeginRecieve работает с разными дочерними потоками, Unity принимает только вызовы игровых объектов/методов, специфичных для Unity, которые должны выполняться в основном потоке. Это сделано для того, чтобы обойти это ограничение и позволить обновлять объекты/пользовательский интерфейс моей сцены после получения пакета данных.

вообще, почему бы не напрямую использовать TcpClient вместо «ручного» управления необработанным сокетом?

derHugo 19.10.2022 17:14

а как реализован ваш UnityMainThreadDispatcher? вместо очереди в этом конкретном случае вы можете использовать Stack и обрабатывать только самое последнее полученное обновление в следующем кадре пользовательского интерфейса ... а затем игнорировать все остальное => очистить стек ... вы, кажется, только заинтересован в последнем снимке, а не в гарантированном и принудительном непрерывном потоке данных ... может быть, даже можно рассмотреть ненадежный UDP в зависимости от размера ваших данных

derHugo 19.10.2022 17:17

@derHugo UnityMainThreadDispatcher — это внешняя библиотека, которую я использую, она позволяет мне запускать специальный код Unity в основном потоке (поскольку Unity это не нравится). Идея заключается в том, что как только мы получим обновленный счетчик, мы сможем обновить набор игровых объектов, используя эти новые данные. С реализацией стека, должен ли я просто объявить глобальный стек, добавить в стек, как только сокет получит данные, и использовать функцию Update основного потока, чтобы дождаться, пока стек не станет> 0 элементов, и обновить на основе последнего вставленного элемента?

Gman0064 19.10.2022 17:30
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как упоминалось в комментариях, я могу представить, что проблема заключается в том, что ваша внешняя библиотека отправляет ненужное количество обновлений с очень высокой частотой кадров.

=> Ваше приложение Unity собирает все эти обновления в Queue из UnityMainThreadDispatcher, которые затем должны перебирать их все в

private void Update()
{
    while (_executionQueue.Count > 0) 
    {
        _executionQueue.Dequeue().Invoke();
    }
}

представьте, что вы получаете 10 обновлений из вашей внешней библиотеки в одном кадре.

=> Ваше приложение Unity должно обновить пользовательский интерфейс с помощью 10 избыточных итераций, поскольку в любом случае пользователю будет видна только самая последняя!

В то же время он задерживает этот кадр => В следующем кадре вы складываете еще больше предметов в Queue

=> Все ваше приложение устаревает все больше и больше, чем дольше внешняя библиотека продолжает спамить его пакетами обновлений.


В вашем случае использования вы, по-видимому, не заботитесь о гарантированном последовательном и непрерывном получении и обработке данных.

Что вам больше нужно, так это то, что ваш пользовательский интерфейс обрабатывает и отображает только самое последнее полученное состояние данных.

Таким образом, я бы не использовал Queue, а скорее ConcurrentStack и обрабатывал бы только последний полученный DataPacket для этого кадра, в противном случае в основном повторял бы структуру диспетчера, например, например.

// A thread-safe Stack
private readonly ConcurrentStack<DataPacket> _lastReceivedDataPackets = new ();

private void Update()
{
    if (_lastReceivedDataPackets.TryPop(out var dataPacket))
    {
        // TODO: Put your UI update here
        UpdateUIWithReceivedData(dataPacket);

        // We don't care about any previous received data -> simply erase them
        _lastReceivedDataPackets.Clear();
    }
}

private void ParseIncomingPacket(DataPacket packet)
{
    ...

    // whatever is the last call to this before the next frame will determine what is displayed in the next frame
    _lastReceivedDataPackets.Push(packet);  
           
    ...
}    
            

Только что реализовал это решение стека для моей текущей итерации кода гарнитуры, и у меня все еще есть проблема, когда слишком много пакетов, полученных гарнитурой, вызывают состояние зависания. Я не уверен, почему это не сработает, поскольку, когда отправляется меньший объем данных, гарнитура отлично справляется с этим. Я не перенес свой код сокета в TcpClient и все еще использую метод socket.BeginRecieve(), как вы думаете, это усугубит проблему? (т. е. создается слишком много потоков для обработки входящих пакетов, что приводит к сбою программы?)

Gman0064 21.10.2022 18:04

Возможные накладные расходы да. В общем, если он должен получать непрерывно, я бы предпочел использовать один постоянный поток. TcpClient в основном просто обертка вокруг сокета, облегчающая вашу жизнь с точки зрения реализации.

derHugo 23.10.2022 08:20

Только что реализовал методы TcpClient в качестве замены моего необработанного интерфейса сокета, и я полагал, что нашел корень проблемы: я не включал какую-то длину пакета для входящих пакетов, и я считаю, что синтаксический анализ JSON все равно однажды дал сбой. сокет был переполнен слишком большим количеством данных. Переключение на tcpclient и добавление проверки длины пакета устранило проблему.

Gman0064 24.10.2022 19:18

Другие вопросы по теме