1

I'm trying to create a TCP client/server program between a python server (first PC) and an UWP client app (second PC). I want to simulate a fast python server that sends a message every 10ms and a slower UWP app with an Update function that has to display the last message received every 40ms.

To do this, on the UWP side, I create a Task that reads the message sent by python, saves it in a variable (this.message), and prints it. The Update function reads this variable and prints it every 40ms (it waits 40ms after each print).

The client/server exchange works but I obtain a strange behavior. The Update function sometimes "blocks" on the same value, see the output:

...
[Task] Message = 1105, Ts = 1626767660488
[Task] Message = 1106, Ts = 1626767660495
[Task] Message = 1107, Ts = 1626767660502
[Task] Message = 1108, Ts = 1626767660508
[Task] Message = 1109, Ts = 1626767660512
[Task] Message = 1110, Ts = 1626767660516
[Task] Message = 1111, Ts = 1626767660519
[Task] Message = 1112, Ts = 1626767660523
[Task] Message = 1113, Ts = 1626767660527
[Task] Message = 1114, Ts = 1626767660530
[Task] Message = 1115, Ts = 1626767660534
[Task] Message = 1116, Ts = 1626767660537
[Task] Message = 1117, Ts = 1626767660541
[Update] Message = 1107
[Task] Message = 1118, Ts = 1626767660546
[Task] Message = 1119, Ts = 1626767660551
[Task] Message = 1120, Ts = 1626767660554
[Task] Message = 1121, Ts = 1626767660558
[Task] Message = 1122, Ts = 1626767660562
[Update] Message = 1122
[Update] Message = 1122
[Task] Message = 1123, Ts = 1626767660693
[Task] Message = 1124, Ts = 1626767660697
[Task] Message = 1125, Ts = 1626767660701
[Task] Message = 1126, Ts = 1626767660705
[Task] Message = 1127, Ts = 1626767660708
[Task] Message = 1128, Ts = 1626767660712
[Task] Message = 1129, Ts = 1626767660716
[Task] Message = 1130, Ts = 1626767660720
[Task] Message = 1131, Ts = 1626767660724
[Task] Message = 1132, Ts = 1626767660727
[Task] Message = 1133, Ts = 1626767660731
[Task] Message = 1134, Ts = 1626767660735
[Task] Message = 1135, Ts = 1626767660739
[Task] Message = 1136, Ts = 1626767660742
[Task] Message = 1137, Ts = 1626767660746
[Update] Message = 1124
[Task] Message = 1138, Ts = 1626767660757
[Task] Message = 1139, Ts = 1626767660760
[Task] Message = 1140, Ts = 1626767660764
[Task] Message = 1141, Ts = 1626767660768
[Task] Message = 1142, Ts = 1626767660772
[Task] Message = 1143, Ts = 1626767660775
[Update] Message = 1143
[Task] Message = 1144, Ts = 1626767660834
[Task] Message = 1145, Ts = 1626767660841
[Task] Message = 1146, Ts = 1626767660848
[Task] Message = 1147, Ts = 1626767660856
[Task] Message = 1148, Ts = 1626767660862
[Task] Message = 1149, Ts = 1626767660869
[Task] Message = 1150, Ts = 1626767660875
...

Python side:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 4444))
print("[SERVER TCP] Waiting connection ...")
sock.listen(1)
client_socket, address = sock.accept()
data = client_socket.recv(1024)
print(data)
print("[SERVER TCP] Connection done")

x = 0
while True:
   x = x + 1
   msg = f"{x}\n"
   client_socket.send(msg.encode())
   sys.stdout.write(msg)
   time.sleep(0.01)

UWP app side (MainPage.xaml.cs):

using System;
using System.IO;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

using System.Threading.Tasks;
using System.Diagnostics;

namespace ClientUWP
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        volatile String message;
        Task T;

        public MainPage()
        {
            this.InitializeComponent();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            this.StartClient();
            this.Update();
        }

        private async void StartClient()
        {
            try
            {
                var streamSocket = new Windows.Networking.Sockets.StreamSocket();
                var hostName = new Windows.Networking.HostName("192.168.1.50");
                string PortNumber = "4444";
                string request = "Hello, World!";

                await streamSocket.ConnectAsync(hostName, PortNumber);

                using (Stream outputStream = streamSocket.OutputStream.AsStreamForWrite())
                {
                    using (var streamWriter = new StreamWriter(outputStream))
                    {
                        await streamWriter.WriteLineAsync(request);
                        await streamWriter.FlushAsync();
                    }
                }

                T = Task.Run(() =>
                {
                    using (Stream inputStream = streamSocket.InputStream.AsStreamForRead())
                    {
                        using (StreamReader streamReader = new StreamReader(inputStream))
                        {
                            while (true)
                            {
                                this.message = streamReader.ReadLine();
                                Debug.WriteLine("[Task] Message = : " + this.message);
                            }
                        }
                    }
                });

            }
            catch (Exception ex)
            {
                Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
                Debug.WriteLine(webErrorStatus.ToString());
            }
        }

        private async void Update()
        {
            while (true)
            {
                Debug.WriteLine("[Update] Message = " + this.message);
                await Task.Delay(40);
            }
            T.Wait();
        }
    }
}

Any idea how to solve this issue? Thanks!

Update Post:

When I print the timestamps in Task, it seems that Task freezes until the end of Update function.

Taevinn
  • 11
  • 2
  • I see 2 possible improvements (did not test yet): 1. remove the volatile and replace it by a lock. Volatile probably does not do what you think it does (check [this answer](https://stackoverflow.com/a/19384758/2983568) and [this link](http://www.albahari.com/threading/part4.aspx#_The_volatile_keyword)). 2. replace the spin-wait `while(true)` loop by a `Threading.Timer` in the `Update` method. Also you should add a `StopWatch` and log the time each line is printed, as this may help understanding when execution switches from one thread to the other. – evilmandarine Jul 20 '21 at 08:07

2 Answers2

0

Your data is essentially a random output. There are many reasons could affect the output, such as the network delay, CPU usage. For example, if your network lag is 160ms, during these 160ms, your client can’t receive messages, but your update method is continued, which causes four update messages will appear continuously.

Therefore, I suggest you don’t separate these two tasks, you could do these in a Task. You could calculate the message numbers instead of using Task.Delay(40) to determine when to print the update message, in other words, you could print an update message every 40 messages

dear_vv
  • 2,350
  • 1
  • 4
  • 13
  • Thanks a lot! I need to separate these two tasks because the `Update` function in this minimal working example simulates the `Update` function of Unity. I also observe varying lags according to the Wifi connection used – Taevinn Jul 20 '21 at 09:52
0

As per Arya's answer, it's almost impossible to guarantee an exact interval between operations because of external factors. As I mentioned in the comments, you can however trigger the reading of the message at more or less regular intervals and avoid reading twice as follows (this is a Console NET Framework app; no need to completely change what you did, I just adapted for non UWP app):

public class Program
{
    public static void Main(string[] args)
    {
        StartTimer();
        StartClient();
        Console.ReadKey();
    }

    private static string message;
    private static readonly Stopwatch sw = new Stopwatch();
    private static readonly object lockobject = new object();
    private static Timer timer;

    private static void StartClient()
    {
        const string ip = "127.0.0.1";
        const int port = 4444;
        
        var tcpClient = new TcpClient();

        try
        {
            var ipep = new IPEndPoint(IPAddress.Parse(ip), port);
            tcpClient.Connect(ipep);

            Task.Run(() => {
                using (var networkStream = tcpClient.GetStream())
                using (var writer = new StreamWriter(networkStream))
                using (var reader = new StreamReader(networkStream, Encoding.UTF8))
                {
                    writer.WriteLine("Hello, World!");
                    writer.Flush();

                    sw.Start();

                    while (true)
                    {
                        try
                        {
                            // Add this lock for synchronization on message between the threads.
                            lock (lockobject)
                            {
                                message = reader.ReadLine();
                                Console.WriteLine($"[Task]  Message: {message} at {sw.ElapsedMilliseconds}ms");
                            }
                        }
                        catch (Exception ex)
                        {
                            // Break the loop and stop timer in case of exception.
                            timer.Change(Timeout.Infinite, Timeout.Infinite);
                            Console.WriteLine(ex);
                            break;
                        }
                    }
                }
            });
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            timer.Change(Timeout.Infinite, Timeout.Infinite);
        }
    }

    private static void StartTimer()
    {
        // Replace the loop by a timer and lock on the read message operation.
        timer = new Timer(_ => 
            {
                lock (lockobject)
                {
                    Console.WriteLine($"[Timer] Message: {message} at { sw.ElapsedMilliseconds}ms");
                }
            }, 
            null, 0, 40);
    }
}

The output with this is as follows:

[Timer] Message:  at 0ms
[Task]  Message: 1 at 0ms
[Task]  Message: 2 at 18ms
[Task]  Message: 3 at 33ms
[Task]  Message: 4 at 49ms
[Task]  Message: 5 at 65ms
[Timer] Message: 5 at 66ms <- timer message
[Task]  Message: 6 at 81ms
[Task]  Message: 7 at 97ms
[Task]  Message: 8 at 112ms
[Timer] Message: 8 at 113ms <- timer message
[Task]  Message: 9 at 128ms
[Task]  Message: 10 at 144ms
[Task]  Message: 11 at 160ms
[Timer] Message: 11 at 161ms <- timer message
[Task]  Message: 12 at 176ms
[Task]  Message: 13 at 192ms
[Task]  Message: 14 at 207ms
[Timer] Message: 14 at 208ms
[Task]  Message: 15 at 223ms
[Task]  Message: 16 at 239ms
[Task]  Message: 17 at 255ms
[Timer] Message: 17 at 256ms
...

You get Timer (former Update) reads at regular intervals and no duplicates. If you need even more accurate intervals, it starts to become much more tricky (see this answer). Not sure this is required for your scenario though.

evilmandarine
  • 4,241
  • 4
  • 17
  • 40
  • Thank you! I replaced the volatile by a lock. I really need that the `Update` function prints the **last** value sent by python. But if there are some network lags (which seems to cause my "strange behavior"), `Task` will be late and the current value printed by `Update` will be late too, right? – Taevinn Jul 20 '21 at 10:18
  • @Taevinn is it not printing the "last" value in my output? Look at the previous message from Task, it is always the same as the Timer message value (here 5, 5, then 8, 8, then 11, 11...). Else I don't really understand what you mean by "last" value. – evilmandarine Jul 20 '21 at 10:31
  • Yes, in your output, the Timer prints the last value saved by Task. But if there are network lags, `Task` could be late with respect to the python server. For instance, python sends 11 but `Task` is still reading 6 and then `Timer` prints 6. In this case, `Timer` doesn't print the "last" value sent by python. – Taevinn Jul 20 '21 at 11:15
  • @Taevinn How's the client supposed to know what message the server sent before it reaches the client? Before it is received, there is no way to know what the server sent, no matter how much lag there is. The code above guarantees you get the last message *received* every 40ms or so, which was what was requested. – evilmandarine Jul 20 '21 at 11:35