1

I am learning C# and I started to learn about things like asynchronous programming, streams and sockets so I tried to experiment with those concepts and made a simple program which is technically two programs with one server side and one client side. the server side is waiting for incoming connection and the client connect to the server and then both client and server can send text to each other and the text will be visible on the other side.

server side:

class Program
    {
        static void Main(string[] args)
        {
            var client = Conection();
            while (true)
            {
                var task1 = Read(client.Result);
                var task2 = Write(client.Result);
                while (!task1.IsCompleted && !task2.IsCompleted)
                {

                }
            }
        }
        async static Task<TcpClient> Conection()
        {
            var ip = IPAddress.Parse("127.0.0.1");
            var port = 23000;
            TcpListener server = new TcpListener(ip, port);
            server.Start();
            var client = await server.AcceptTcpClientAsync();
            return client;
        }
        async static Task Read(TcpClient cli)
        {
            var stream = cli.GetStream();
            var reader = new StreamReader(stream);
            char[] buff = new char[64];
            await reader.ReadAsync(buff, 0, buff.Length);
            string text = new string(buff);
            Console.WriteLine(text);
        }
        async static Task Write(TcpClient cli)
        {
            var stream = cli.GetStream();
            var writer = new StreamWriter(stream);
            var message = await Task.Run(() => Console.ReadLine());
            writer.WriteLine(message);
            writer.Flush();
        }
    }

client side:

class Program
    {
        static void Main(string[] args)
        {
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            var client = new TcpClient();
            client.Connect(ip, 23000);
            var stream = client.GetStream();
            var reader = new StreamReader(stream);
            var writer = new StreamWriter(stream);
            while (true)
            {
                var task1 = Read(reader);
                var task2 = Write(writer);
                while(!task1.IsCompleted && !task2.IsCompleted)
                {

                }
            }
        }
        async static Task Write(StreamWriter wr)
        {
            var str = await Task.Run(() => Console.ReadLine());
            wr.Write(str);
            wr.Flush();
        }
        async static Task Read(StreamReader reader)
        {
            var str = await reader.ReadLineAsync();
            Console.WriteLine(str);
        }
    }

It works fine but something looks wrong when I check in the task manager, it looks like in the client side the RAM usage is constantly rising without stopping just after sending some short text from the client or server side and the CPU usage is more than 11% in both the client and the server side...

Can I make this code more efficient in terms of using RAM and CPU?, or I simply did it all in the wrong way?.

bombadil
  • 23
  • 2
  • About the RAM usage: in your `while(true)` loops you are calling functions which create objects: the `var ... = `. These objects keep being created as the functions keep being called, so the RAM usage skyrockets. Instead, use global variables, so only few objects exist in memory – Ergis Dec 13 '20 at 23:40
  • The var statements shouldn't be that serious, Task is a lightweight object that's designed to be created and discarded frequently - and even if they were bulky objects the moment each while loop finishes the 'var' is marked for disposal as it's context is lost, it's not likely to be causing issues – The Lemon Dec 13 '20 at 23:46
  • Shouldn't be serious? I mean...the RAM is skyrocketing...:) – Ergis Dec 13 '20 at 23:49
  • Do you know what `IDisposable.Dispose` is for? – mjwills Dec 14 '20 at 00:25
  • @mjwills No. I am still learning and there was nothing about IDisposable.Dispose in the videos and articles I have read about C# – bombadil Dec 14 '20 at 10:09

2 Answers2

4

Your while loops look very concerning. 'While(statement){}' will cause your code to check the 'statement' as fast as your CPU is capable of checking it repeatedly until it finally changes. That's most likely what's causing your strange behaviour. If you want to have that sort of logic, it can help to have a delay between each check -

while (!task1.IsCompleted && !task2.IsCompleted)
{
     await Task.Delay(100);
}

Doing something like that would solve your problem, except it's still terrible coding, because you're using a while loop when Async Tasks have inbuilt waiting methods.

You could use .Wait() instead to bipass the entire loop, which would be an OK solution

 while (true)
  {
      var task1 = Read(reader);
      var task2 = Write(writer);
      task1.Wait();
      task2.Wait();   
  }

My preferred solution however, would be to make main an async method and to await task1 and task2

 while (true)
  {
      var task1 = Read(reader);
      var task2 = Write(writer);
      await task1;
      await task2;   
  }

or if main can't be async then to put all of the code in Main into a second method (that is async) and then within main you would just call your second method with a .wait statement

The Lemon
  • 1,211
  • 15
  • 26
4

This is a bad sign:

while(!task1.IsCompleted && !task2.IsCompleted)

You have designed your own spin wait, that takes up a whole core in a never-ending loop waiting for keyboard input. It's hard to see what you want to achieve here. However this would be a more sane approach:

static async Task Main(string[] args)
{
   var ip = IPAddress.Parse("127.0.0.1");
   var client = new TcpClient();
   await client.ConnectAsync(ip, 23000);
   var stream = client.GetStream();
   var reader = new StreamReader(stream);
   var writer = new StreamWriter(stream);
   while (true)
   {
      await ReadAsync(reader);
      await WriteAsync(writer);
   }
}

private static async Task WriteAsync(StreamWriter wr)
{
   var str = Console.ReadLine();
   await wr.WriteAsync(str);
   await wr.FlushAsync();
}

private static async Task ReadAsync(StreamReader reader)
{
   var str = await reader.ReadLineAsync();
   Console.WriteLine(str);
}

If you wanted to read from the stream asynchronously, you might try something like this

static async Task Main(string[] args)
{
   var ip = IPAddress.Parse("127.0.0.1");
   var client = new TcpClient();
   await client.ConnectAsync(ip, 23000);
   var stream = client.GetStream();
   using var reader = new StreamReader(stream);
   await using var writer = new StreamWriter(stream);
   using var cs = new CancellationTokenSource();

   var readTask = ReadAsync(reader,cs.Token);

   while (!cs.Token.IsCancellationRequested)
   {
      var str = Console.ReadLine();

      if (string.IsNullOrWhiteSpace(str))
      {
         cs.Cancel();
      }
      await writer.WriteAsync(str);
      await writer.FlushAsync();
   }

   await readTask;

   Console.WriteLine("Finished");
}

private static async Task ReadAsync(TextReader sr, CancellationToken token)
{
   while (!token.IsCancellationRequested)
   {
      var str = await sr.ReadLineAsync();
      Console.WriteLine(str);
   }
}

.net 7.3 compliant

private static async Task Main(string[] args)
{
   var ip = IPAddress.Parse("127.0.0.1");
   var client = new TcpClient();
   await client.ConnectAsync(ip, 23000);
   var stream = client.GetStream();
   using (var reader = new StreamReader(stream))
   using (var writer = new StreamWriter(stream))
   using (var cs = new CancellationTokenSource())
   {
      // starts a task hot, does not await
      var readTask = ReadAsync(reader, cs.Token);

      // goes in to a loop
      while (!cs.Token.IsCancellationRequested)
      {
         var str = Console.ReadLine();

         if (string.IsNullOrWhiteSpace(str))
         {
            cs.Cancel();
         }

         await writer.WriteAsync(str);
         await writer.FlushAsync();
      }

      await readTask;

      Console.WriteLine("Finished");
   }

}

private static async Task ReadAsync(TextReader sr, CancellationToken token)
{
   // read loop
   while (!token.IsCancellationRequested)
   {
      var str = await sr.ReadLineAsync();
      Console.WriteLine(str);
   }
}
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • I have a hard time understanding async main method. From what I understood async methods run synchronously until the await keyword and then the control return to the method that called the async method and the rest of the async method runs when the awaitable task is finished. so what is going on with an async main method? how the rest of the code is executing after an await in an async main method?. – bombadil Dec 14 '20 at 17:47
  • @bombadil the async main method just allows you to await. in the second example, you are running async read asynchronously, and only awaiting it, after you cancel (just to clean up) – TheGeneral Dec 14 '20 at 21:05
  • Is this code actually makes you able to send and receive text simultaneously like my code?. I don't fully understand your code but it looks to me like it blocks in the var str and the "finished" parts.. I also can't test this code on my visual studio and it looks like it have a problem with the "await using" part. – bombadil Dec 15 '20 at 22:51
  • @bombadil yes this is for the latest version of C#, what version are using ? i can give you one without the await using, and yes it reading and writing simultaneously – TheGeneral Dec 15 '20 at 22:55
  • according the the error message I am using version 7.3. How your code doesn't block at "str = Console.ReadLine()" or "Console.WriteLine("Finished")"?. – bombadil Dec 15 '20 at 23:52
  • @bombadil updated, with a comment to show why it works – TheGeneral Dec 16 '20 at 00:06
  • I suspect he intended to start the reader and writer then wait for them both. `Task.WhenAll(Read(reader), Write(writer))` – Jeremy Lakeman Dec 16 '20 at 00:20
  • @JeremyLakeman yeah this is likely the case. – TheGeneral Dec 16 '20 at 00:24
  • @bombadil regarding the `async Main` method, it is explained briefly here: [What is the point of having async Main?](https://stackoverflow.com/questions/46219114/what-is-the-point-of-having-async-main) – Theodor Zoulias Dec 16 '20 at 06:15
  • I noticed that without the using statement your code use much more RAM and the RAM usage keep increasing just like my code. I read about the using statement but I still don't understand how exactly the using statement is reducing RAM usage in your code – bombadil Dec 16 '20 at 22:04
  • @bombadil things that can be disposed need to be disposed (aka the using statement) if you dont they can have managed and unmanaged resources that stick around in memory until a full garbage collect (or worse). always dispose if its disposable. or even better always use a using statement if possible – TheGeneral Dec 16 '20 at 23:37