В настоящее время я работаю над исследовательским проектом, который включает получение данных в реальном времени (до 10 циклов в секунду) с внешнего сканера и их отображение на HoloLens 2 с помощью Unity. В настоящее время система разделена на две части:
Часть 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, которые должны выполняться в основном потоке. Это сделано для того, чтобы обойти это ограничение и позволить обновлять объекты/пользовательский интерфейс моей сцены после получения пакета данных.
а как реализован ваш UnityMainThreadDispatcher
? вместо очереди в этом конкретном случае вы можете использовать Stack
и обрабатывать только самое последнее полученное обновление в следующем кадре пользовательского интерфейса ... а затем игнорировать все остальное => очистить стек ... вы, кажется, только заинтересован в последнем снимке, а не в гарантированном и принудительном непрерывном потоке данных ... может быть, даже можно рассмотреть ненадежный UDP в зависимости от размера ваших данных
@derHugo UnityMainThreadDispatcher
— это внешняя библиотека, которую я использую, она позволяет мне запускать специальный код Unity в основном потоке (поскольку Unity это не нравится). Идея заключается в том, что как только мы получим обновленный счетчик, мы сможем обновить набор игровых объектов, используя эти новые данные. С реализацией стека, должен ли я просто объявить глобальный стек, добавить в стек, как только сокет получит данные, и использовать функцию Update
основного потока, чтобы дождаться, пока стек не станет> 0 элементов, и обновить на основе последнего вставленного элемента?
Как упоминалось в комментариях, я могу представить, что проблема заключается в том, что ваша внешняя библиотека отправляет ненужное количество обновлений с очень высокой частотой кадров.
=> Ваше приложение 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()
, как вы думаете, это усугубит проблему? (т. е. создается слишком много потоков для обработки входящих пакетов, что приводит к сбою программы?)
Возможные накладные расходы да. В общем, если он должен получать непрерывно, я бы предпочел использовать один постоянный поток. TcpClient
в основном просто обертка вокруг сокета, облегчающая вашу жизнь с точки зрения реализации.
Только что реализовал методы TcpClient
в качестве замены моего необработанного интерфейса сокета, и я полагал, что нашел корень проблемы: я не включал какую-то длину пакета для входящих пакетов, и я считаю, что синтаксический анализ JSON все равно однажды дал сбой. сокет был переполнен слишком большим количеством данных. Переключение на tcpclient и добавление проверки длины пакета устранило проблему.
вообще, почему бы не напрямую использовать TcpClient вместо «ручного» управления необработанным сокетом?