Begin / EndInvoke не синхронизируется с основным потоком - PullRequest
0 голосов
/ 04 августа 2020

Я создаю сетевую игру в Unity, используя сокеты на основе tcp. (Я новичок в сетевых технологиях и многопоточности).

Я использую System. Net .Sockets asyn c методы, такие как socket.BeginReceive () и socket.EndReceive (). Все соединения клиент-сервер и обмен сообщениями работают. Но как только я пытаюсь получить доступ к чему-либо из Monobehavior (чтобы я действительно мог повлиять на игру Unity), например, к преобразованию игрового объекта, возникает исключение, сообщающее мне, что я могу получить доступ к этим свойствам только из основного потока.

Мой вопрос: почему я не вернусь к основному потоку в обратном вызове foo.beginRecieve () или, по крайней мере, после того, как я вызываю foo.EndReceive ()? Как мне вернуться в основной поток с помощью API сокета asyn c? Придется ли мне в конечном итоге использовать api синхронного сокета и просто обрабатывать потоки самостоятельно, чтобы я мог правильно повторно синхронизировать c с основным потоком Unity?

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

//code which sets up the callbacks which are executed when a client receives a message from the server
void BeginReceive() => _clientSocket.BeginReceive(_messageReceivedBuffer, 0, _messageReceivedBuffer.Length, SocketFlags.None, ReceiveCallback, null);
 
void ReceiveCallback(IAsyncResult result)
 {
    _clientSocket.EndReceive(result);
 
    var msg = _serializer.ByteArrayToObject<NetworkMessage>(_messageReceivedBuffer);

    //this clientmanipulation manipulates the game grid and the gameobjects' which it references
    //it's in this method that an exception gets thrown and the code breaks
    msg.ClientManipulation(_gameGrid);
         
    BeginReceive();
}

1 Ответ

0 голосов
/ 04 августа 2020

Обычно для EndReceive:

Перед вызовом BeginReceive вам необходимо создать метод обратного вызова, реализующий делегат AsyncCallback. Этот метод обратного вызова выполняется в отдельном потоке и вызывается системой после возврата BeginReceive. Метод обратного вызова должен принимать IAsyncResult, возвращаемый методом BeginReceive, в качестве параметра.

[...]

Метод EndReceive будет блокироваться до тех пор, пока данные не будут доступно.

Обычно вы использовали бы шаблон, часто называемый диспетчером основного потока, с использованием ConcurrentQueue. Для Unity это довольно просто, поскольку у вас уже есть что-то, что наверняка всегда выполнялось в основном потоке: Update

public class Example : MonoBehaviour
{
    ...

    private ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();

    private void Update()
    {
        // Handle all callbacks in main thread
        while(_mainthreadActions.Count > 0 && _mainThreadActions.TryDequeue(out var action))
        {
            action?.Invoke();
        }
    }

    void BeginReceive()
    {
        _clientSocket.BeginReceive(_messageReceivedBuffer, 0, _messageReceivedBuffer.Length, SocketFlags.None, ReceiveCallback, null); 
    }
 
    void ReceiveCallback(IAsyncResult result)
    {
        _clientSocket.EndReceive(result);
 
        var msg = _serializer.ByteArrayToObject<NetworkMessage>(_messageReceivedBuffer);

        // On threads / possibly async code enqueue the action to be invoked in the main thread
        _mainThreadActions.Enqueue(()=> {msg.ClientManipulation(_gameGrid)});
         
        BeginReceive();
    }
}
...