WedX - журнал о программировании и компьютерных науках

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

Я изучаю 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 % как на стороне клиента, так и на стороне сервера...

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

13.12.2020

  • Об использовании оперативной памяти: в ваших while(true) циклах вы вызываете функции, которые создают объекты: var ... = . Эти объекты продолжают создаваться по мере того, как функции продолжают вызываться, поэтому использование оперативной памяти резко возрастает. Вместо этого используйте глобальные переменные, чтобы в памяти существовало только несколько объектов. 14.12.2020
  • Операторы var не должны быть такими серьезными, Task — это легкий объект, который предназначен для частого создания и удаления, и даже если они были громоздкими объектами, в момент завершения каждого цикла while «var» помечается для удаления, поскольку его контекст теряется. , вряд ли это вызовет проблемы 14.12.2020
  • Не должно быть серьезно? Я имею в виду... оперативная память стремительно растет... :) 14.12.2020
  • Вы знаете, для чего нужен IDisposable.Dispose? 14.12.2020
  • @mjwills Нет. Я все еще учусь, и в видео и статьях, которые я читал о C#, ничего не было об IDisposable.Dispose. 14.12.2020

Ответы:


1

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

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);
   }
}
13.12.2020
  • Мне трудно понять асинхронный основной метод. Из того, что я понял, асинхронные методы выполняются синхронно до тех пор, пока не будет ключевое слово await, а затем управление вернется к методу, вызвавшему асинхронный метод, а остальная часть асинхронного метода запустится, когда ожидаемая задача будет завершена. так что же происходит с асинхронным основным методом? как выполняется остальная часть кода после ожидания в асинхронном основном методе? 14.12.2020
  • @bombadil асинхронный основной метод просто позволяет вам ждать. во втором примере вы запускаете асинхронное чтение асинхронно и ожидаете его только после отмены (просто для очистки) 15.12.2020
  • Действительно ли этот код позволяет вам отправлять и получать текст одновременно, как мой код? Я не совсем понимаю ваш код, но мне кажется, что он блокируется в var str и готовых частях. Я также не могу протестировать этот код в своей визуальной студии, и похоже, что у него проблема с использованием ожидания часть. 16.12.2020
  • @bombadil да, это для последней версии C #, какую версию вы используете? я могу дать вам один без использования ожидания, и да, он читает и пишет одновременно 16.12.2020
  • согласно сообщению об ошибке, я использую версию 7.3. Как ваш код не блокируется на str = Console.ReadLine() или Console.WriteLine(Finished)?. 16.12.2020
  • Обновление @bombadil с комментарием, показывающим, почему это работает 16.12.2020
  • Я подозреваю, что он намеревался сначала запустить читателя и писателя, а затем дождаться их обоих. Task.WhenAll(Read(reader), Write(writer)) 16.12.2020
  • @JeremyLakeman да, скорее всего, это так. 16.12.2020
  • @bombadil относительно метода async Main, здесь кратко объясняется: смысл иметь асинхронный Main? 16.12.2020
  • Я заметил, что без оператора using ваш код использует гораздо больше ОЗУ, и использование ОЗУ продолжает увеличиваться, как и мой код. Я читал об операторе использования, но до сих пор не понимаю, как именно оператор использования уменьшает использование ОЗУ в вашем коде. 17.12.2020
  • Вещи @bombadil, которые могут быть удалены, должны быть удалены (также известные как оператор использования), если вы этого не сделаете, они могут иметь управляемые и неуправляемые ресурсы, которые остаются в памяти до полной сборки мусора (или хуже). всегда утилизируйте, если он одноразовый. или, что еще лучше, всегда используйте оператор using, если это возможно 17.12.2020

  • 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

    13.12.2020
    Новые материалы

    Как создать диаграмму градиентной кисти с помощью D3.js
    Резюме: Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с..

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

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

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

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

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..


    Для любых предложений по сайту: [email protected]