Skip to main content
added 180 characters in body
Source Link
Theraot
  • 28.3k
  • 4
  • 55
  • 83

Please note that the slow client problem can still have an impact even if you separate simulation and network, because if the network is waiting on a client, it will not take input from the other clients. Errata: This is only a problem if you are trying to listen from each client in a loop. However, the way it is done is to just listen on a port, not from any particular source.

Please note that the slow client problem can still have an impact even if you separate simulation and network, because if the network is waiting on a client, it will not take input from the other clients.

Please note that the slow client problem can still have an impact even if you separate simulation and network, because if the network is waiting on a client, it will not take input from the other clients. Errata: This is only a problem if you are trying to listen from each client in a loop. However, the way it is done is to just listen on a port, not from any particular source.

added 4106 characters in body
Source Link
Theraot
  • 28.3k
  • 4
  • 55
  • 83

If the simulation is running on a separate thread it should not be dealing with connections. Instead it needneeds a way to get all the network input since last iteration.

Note: just because you used one way to get input from the network, does not mean you have to use the same way to send updates. In fact, sending updates has extra considerations. However, I would recommend to start by making the communication between simulation and network symmetric (I mean, use the same approach).

You can then, each loop, Wait, check if the task completed. If it did, take the result, and recreate the task. That is how you do it in a single thread.

NoteNotes: When you are not receiving, the socket is buffering.

  • When you are not receiving, the socket is buffering.
  • If you have multiple tasks, you can use WaitAny.

You can add a continuation, or better yet, wrap it all in an async method. Yes, I said to not await the task, that is so you can pass parameters to Wait, but that does not mean you cannot wrap it in an async method. - then that continuation will take the input from the network, passpost it to whatever object you need it into (for example a channel object).

Each loop, you will stilltake the posted data (yes, that could be in another thread), and check if you need to recreate the task.

If the simulation is running on a separate thread it should not be dealing with connections. Instead it need a way to get all the network input since last iteration.

Note: just because you used one way to get input from the network, does not mean you have to use the same way to send updates. In fact, sending updates has extra considerations. However, I would recommend to start by making the communication between simulation and network symmetric.

You can then, each loop, check if the task completed. If it did, take the result, and recreate the task. That is how you do it in a single thread.

Note: When you are not receiving, the socket is buffering.

You can add a continuation, or better yet, wrap it all in an async method. Yes, I said to not await the task, that is so you can pass parameters to Wait, but that does not mean you cannot wrap it in an async method. - then that continuation will take the input from the network, pass it to whatever object you need it into (for example a channel object).

Each loop, you will still check if you need to recreate the task.

If the simulation is running on a separate thread it should not be dealing with connections. Instead it needs a way to get all the network input since last iteration.

Note: just because you used one way to get input from the network, does not mean you have to use the same way to send updates. In fact, sending updates has extra considerations. However, I would recommend to start by making the communication between simulation and network symmetric (I mean, use the same approach).

You can then, each loop, Wait, check if the task completed. If it did, take the result, and recreate the task. That is how you do it in a single thread.

Notes:

  • When you are not receiving, the socket is buffering.
  • If you have multiple tasks, you can use WaitAny.

You can add a continuation, or better yet, wrap it all in an async method. Yes, I said to not await the task, that is so you can pass parameters to Wait, but that does not mean you cannot wrap it in an async method. - then that continuation will take the input from the network, post it to whatever object you need it into (for example a channel object).

Each loop, you will take the posted data (yes, that could be in another thread), and check if you need to recreate the task.

added 4106 characters in body
Source Link
Theraot
  • 28.3k
  • 4
  • 55
  • 83

On separating threads and the slow client

First of all. Yes, there is a way to do it all in a single thread, and it will work. Will come back to the problem of the slow client.


Simulation Thread and Network Thread

If the simulation is running on a separate thread it should not be dealing with connections. Instead it need a way to get all the network input since last iteration.


One way to do that is to have the network thread write to an network update object that holds a datastructure where all the network events are stored (commands from clients, disconnections, and so on). Then, when the simulation thread wants to get them, it can swap the object for a new one (which can be done atomically).

That also means that the network thread cannot be allowed to cache the reference to the network update object. Thus, all access to the network update object will have to be volatile.


Another way to do that is to have "channel" objects. Either a ConcurrentBag<T> or a ConcurrentQueue<T> will work (they are not so different). I would avoid using the BlockingCollection<T> for this purpose (because it is blocking). Then the Network thread will push/add into the channel the network events, and the simulation thread can pick them up when it loops.


Note: just because you used one way to get input from the network, does not mean you have to use the same way to send updates. In fact, sending updates has extra considerations. However, I would recommend to start by making the communication between simulation and network symmetric.


On the slow client problem

Please note that the slow client problem can still have an impact even if you separate simulation and network, because if the network is waiting on a client, it will not take input from the other clients.

Also note that you will inevitably open a socket to receive. Unless you are using a library designed for this, I recommend to stay in control of the socket.

With that in mind, you do not have to use a blocking API to receive. Regardless of how many threads the server is using. Ideally you use something like ReceiveAsync that gives you a Task.

Once you have a Task that will complete when you receives data from a client, you have a few options...


First option:

You can Wait (not await) with a timeout (and even a CancellationToken, useful to teardown the server). That way you know that regardless how slow the client is, the network thread will not be stuck for long.

You can then, each loop, check if the task completed. If it did, take the result, and recreate the task. That is how you do it in a single thread.

Note: When you are not receiving, the socket is buffering.


Second Option:

You can add a continuation, or better yet, wrap it all in an async method. Yes, I said to not await the task, that is so you can pass parameters to Wait, but that does not mean you cannot wrap it in an async method. - then that continuation will take the input from the network, pass it to whatever object you need it into (for example a channel object).

Each loop, you will still check if you need to recreate the task.


Third option:

Following the same idea, you can recreate the task inside of the continuation. So that you do not have to worry about it each loop.

In fact, if you started with a single threaded solution and moved down this path. From this point, it just a matter of using a well placed Task.Run (if you did not do it already) and you have a separated thread for the network (if you are using Task.Run mark it LongRunning, so it is a dedicated thread and not one from the ThreadPool).


On separating threads and the slow client

First of all. Yes, there is a way to do it all in a single thread, and it will work. Will come back to the problem of the slow client.


Simulation Thread and Network Thread

If the simulation is running on a separate thread it should not be dealing with connections. Instead it need a way to get all the network input since last iteration.


One way to do that is to have the network thread write to an network update object that holds a datastructure where all the network events are stored (commands from clients, disconnections, and so on). Then, when the simulation thread wants to get them, it can swap the object for a new one (which can be done atomically).

That also means that the network thread cannot be allowed to cache the reference to the network update object. Thus, all access to the network update object will have to be volatile.


Another way to do that is to have "channel" objects. Either a ConcurrentBag<T> or a ConcurrentQueue<T> will work (they are not so different). I would avoid using the BlockingCollection<T> for this purpose (because it is blocking). Then the Network thread will push/add into the channel the network events, and the simulation thread can pick them up when it loops.


Note: just because you used one way to get input from the network, does not mean you have to use the same way to send updates. In fact, sending updates has extra considerations. However, I would recommend to start by making the communication between simulation and network symmetric.


On the slow client problem

Please note that the slow client problem can still have an impact even if you separate simulation and network, because if the network is waiting on a client, it will not take input from the other clients.

Also note that you will inevitably open a socket to receive. Unless you are using a library designed for this, I recommend to stay in control of the socket.

With that in mind, you do not have to use a blocking API to receive. Regardless of how many threads the server is using. Ideally you use something like ReceiveAsync that gives you a Task.

Once you have a Task that will complete when you receives data from a client, you have a few options...


First option:

You can Wait (not await) with a timeout (and even a CancellationToken, useful to teardown the server). That way you know that regardless how slow the client is, the network thread will not be stuck for long.

You can then, each loop, check if the task completed. If it did, take the result, and recreate the task. That is how you do it in a single thread.

Note: When you are not receiving, the socket is buffering.


Second Option:

You can add a continuation, or better yet, wrap it all in an async method. Yes, I said to not await the task, that is so you can pass parameters to Wait, but that does not mean you cannot wrap it in an async method. - then that continuation will take the input from the network, pass it to whatever object you need it into (for example a channel object).

Each loop, you will still check if you need to recreate the task.


Third option:

Following the same idea, you can recreate the task inside of the continuation. So that you do not have to worry about it each loop.

In fact, if you started with a single threaded solution and moved down this path. From this point, it just a matter of using a well placed Task.Run (if you did not do it already) and you have a separated thread for the network (if you are using Task.Run mark it LongRunning, so it is a dedicated thread and not one from the ThreadPool).

Source Link
Theraot
  • 28.3k
  • 4
  • 55
  • 83
Loading