Charly
Charly

Reputation: 470

Begin/EndInvoke not synchronizing with main thread

I'm creating a networked game in Unity using tcp based sockets. (I'm new to networking and threading stuff).

I'm using System.Net.Sockets async methods like socket.BeginReceive() and socket.EndReceive(). All the client-server connecting and messaging works. But as soon as I try access anything from a Monobehavior (so that I can actually have any effect on the Unity game), like a gameobject's transform, an exception is thrown telling me that I can only access these properties from the main thread.

My question is: why am I not back on the main thread in the callback to foo.beginRecieve(), or at least after I call foo.EndReceive()? How do I return to the main thread using the async socket api? Will I end up having to use the synchronous socket api and just handle the threading myself so I can properly resync with Unity's main thread?

Thanks! Any help would be much appreciated.

//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();
}

Upvotes: 0

Views: 424

Answers (1)

derHugo
derHugo

Reputation: 90823

In general for EndReceive:

Before calling BeginReceive, you need to create a callback method that implements the AsyncCallback delegate. This callback method executes in a separate thread and is called by the system after BeginReceive returns. The callback method must accept the IAsyncResult returned by the BeginReceive method as a parameter.

[...]

The EndReceive method will block until data is available.

Usually you would use a pattern often referred to as Main Thread Dispatcher using a ConcurrentQueue. For Unity this is quite easy since you already have something that is surely always been executed in the main thread: 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();
    }
}

Upvotes: 0

Related Questions