Почему моя маленькая программа продолжает использовать все больше и больше оперативной памяти и большую часть процессора?

Я изучаю C# и начал узнавать о таких вещах, как асинхронное программирование, потоки и сокеты, поэтому я попытался поэкспериментировать с этими концепциями и сделал простую программу, которая технически представляет собой две программы с одной серверной и одной клиентской сторонами. сторона сервера ожидает входящего соединения, и клиент подключается к серверу, а затем и клиент, и сервер могут отправлять текст друг другу, и текст будет виден на другой стороне.

серверная часть:

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

сторона клиента:

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

Он работает нормально, но что-то выглядит не так, когда я проверяю диспетчер задач, похоже, что на стороне клиента использование ОЗУ постоянно растет без остановки сразу после отправки короткого текста со стороны клиента или сервера, а использование ЦП превышает 11 % как на стороне клиента, так и на стороне сервера...

Могу ли я сделать этот код более эффективным с точки зрения использования оперативной памяти и процессора? Или я просто сделал все неправильно?

Об использовании оперативной памяти: в ваших while(true) циклах вы вызываете функции, которые создают объекты: var ... = . Эти объекты продолжают создаваться по мере того, как функции продолжают вызываться, поэтому использование оперативной памяти резко возрастает. Вместо этого используйте глобальные переменные, чтобы в памяти существовало только несколько объектов.

Ergis 14.12.2020 00:40

Операторы var не должны быть такими серьезными, Task — это легкий объект, который предназначен для частого создания и удаления, и даже если они были громоздкими объектами, в момент завершения каждого цикла while «var» помечается для удаления, поскольку его контекст теряется. , вряд ли это вызовет проблемы

The Lemon 14.12.2020 00:46

Не должно быть серьезно? Я имею в виду... оперативная память стремительно растет... :)

Ergis 14.12.2020 00:49

А вы знаете, для чего нужен IDisposable.Dispose?

mjwills 14.12.2020 01:25

@mjwills Нет. Я все еще учусь, и в видео и статьях, которые я читал о C#, ничего не было об IDisposable.Dispose.

bombadil 14.12.2020 11:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
133
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваши циклы while выглядят очень подозрительно. «While(statement){}» заставит ваш код проверять «оператор» настолько быстро, насколько ваш ЦП способен многократно проверять его, пока он окончательно не изменится. Это, скорее всего, и является причиной вашего странного поведения. Если вы хотите иметь такую ​​логику, может помочь задержка между каждой проверкой -

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

Выполнение чего-то подобного решит вашу проблему, за исключением того, что это все еще ужасное кодирование, потому что вы используете цикл while, когда асинхронные задачи имеют встроенные методы ожидания.

Вместо этого вы можете использовать .Wait() для обхода всего цикла, что было бы правильным решением.

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

Однако моим предпочтительным решением было бы сделать основной асинхронный метод и ждать задачи1 и задачи2.

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

или если main не может быть асинхронным, то поместить весь код в Main во второй метод (то есть асинхронный), а затем в main вы просто вызовете свой второй метод с оператором .wait

Ответ принят как подходящий

Это плохой знак:

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

Вы разработали собственное ожидание вращения, которое занимает все ядро ​​​​в бесконечном цикле ожидания ввода с клавиатуры. Трудно понять, чего вы хотите достичь здесь. Однако это был бы более разумный подход:

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

Если вы хотите читать из потока асинхронно, вы можете попробовать что-то вроде этого

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 совместимый

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

Мне трудно понять асинхронный основной метод. Насколько я понял, асинхронные методы выполняются синхронно до тех пор, пока не будет ключевое слово await, а затем управление вернется к методу, вызвавшему асинхронный метод, а остальная часть асинхронного метода запустится, когда ожидаемая задача будет завершена. так что же происходит с асинхронным основным методом? как выполняется остальная часть кода после ожидания в асинхронном основном методе?

bombadil 14.12.2020 18:47

@bombadil асинхронный основной метод просто позволяет вам ждать. во втором примере вы запускаете асинхронное чтение асинхронно и ожидаете его только после отмены (просто для очистки)

TheGeneral 14.12.2020 22:05

Действительно ли этот код позволяет вам отправлять и получать текст одновременно, как мой код? Я не совсем понимаю ваш код, но мне кажется, что он блокируется в var str и «готовых» частях. Я также не могу протестировать этот код в своей визуальной студии, и похоже, что у него проблема с "ждите использования" часть.

bombadil 15.12.2020 23:51

@bombadil да, это для последней версии C#, какую версию вы используете? я могу дать вам один без использования ожидания, и да, он читает и пишет одновременно

TheGeneral 15.12.2020 23:55

согласно сообщению об ошибке, я использую версию 7.3. Как ваш код не блокируется в "str = Console.ReadLine()" или "Console.WriteLine("Finished")"?.

bombadil 16.12.2020 00:52

Обновление @bombadil с комментарием, показывающим, почему это работает

TheGeneral 16.12.2020 01:06

Я подозреваю, что он намеревался сначала запустить читателя и писателя, а затем дождаться их обоих. Task.WhenAll(Read(reader), Write(writer))

Jeremy Lakeman 16.12.2020 01:20

@JeremyLakeman да, скорее всего, это так.

TheGeneral 16.12.2020 01:24

@bombadil относительно метода async Main, он кратко объясняется здесь: Какой смысл иметь асинхронный Main?

Theodor Zoulias 16.12.2020 07:15

Я заметил, что без оператора using ваш код использует гораздо больше ОЗУ, и использование ОЗУ продолжает увеличиваться, как и мой код. Я читал об операторе использования, но до сих пор не понимаю, как именно оператор использования уменьшает использование ОЗУ в вашем коде.

bombadil 16.12.2020 23:04

Вещи @bombadil, которые могут быть удалены, должны быть удалены (также известные как оператор использования), если вы этого не сделаете, они могут иметь управляемые и неуправляемые ресурсы, которые остаются в памяти до полной сборки мусора (или хуже). всегда утилизируйте, если он одноразовый. или, что еще лучше, всегда используйте оператор using, если это возможно

TheGeneral 17.12.2020 00:37

Другие вопросы по теме