Я изучаю 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 % как на стороне клиента, так и на стороне сервера...
Могу ли я сделать этот код более эффективным с точки зрения использования оперативной памяти и процессора? Или я просто сделал все неправильно?
Операторы var не должны быть такими серьезными, Task — это легкий объект, который предназначен для частого создания и удаления, и даже если они были громоздкими объектами, в момент завершения каждого цикла while «var» помечается для удаления, поскольку его контекст теряется. , вряд ли это вызовет проблемы
Не должно быть серьезно? Я имею в виду... оперативная память стремительно растет... :)
А вы знаете, для чего нужен IDisposable.Dispose
?
@mjwills Нет. Я все еще учусь, и в видео и статьях, которые я читал о C#, ничего не было об IDisposable.Dispose.
Ваши циклы 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 асинхронный основной метод просто позволяет вам ждать. во втором примере вы запускаете асинхронное чтение асинхронно и ожидаете его только после отмены (просто для очистки)
Действительно ли этот код позволяет вам отправлять и получать текст одновременно, как мой код? Я не совсем понимаю ваш код, но мне кажется, что он блокируется в var str и «готовых» частях. Я также не могу протестировать этот код в своей визуальной студии, и похоже, что у него проблема с "ждите использования" часть.
@bombadil да, это для последней версии C#, какую версию вы используете? я могу дать вам один без использования ожидания, и да, он читает и пишет одновременно
согласно сообщению об ошибке, я использую версию 7.3. Как ваш код не блокируется в "str = Console.ReadLine()" или "Console.WriteLine("Finished")"?.
Обновление @bombadil с комментарием, показывающим, почему это работает
Я подозреваю, что он намеревался сначала запустить читателя и писателя, а затем дождаться их обоих. Task.WhenAll(Read(reader), Write(writer))
@JeremyLakeman да, скорее всего, это так.
@bombadil относительно метода async Main
, он кратко объясняется здесь: Какой смысл иметь асинхронный Main?
Я заметил, что без оператора using ваш код использует гораздо больше ОЗУ, и использование ОЗУ продолжает увеличиваться, как и мой код. Я читал об операторе использования, но до сих пор не понимаю, как именно оператор использования уменьшает использование ОЗУ в вашем коде.
Вещи @bombadil, которые могут быть удалены, должны быть удалены (также известные как оператор использования), если вы этого не сделаете, они могут иметь управляемые и неуправляемые ресурсы, которые остаются в памяти до полной сборки мусора (или хуже). всегда утилизируйте, если он одноразовый. или, что еще лучше, всегда используйте оператор using, если это возможно
Об использовании оперативной памяти: в ваших
while(true)
циклах вы вызываете функции, которые создают объекты:var ... =
. Эти объекты продолжают создаваться по мере того, как функции продолжают вызываться, поэтому использование оперативной памяти резко возрастает. Вместо этого используйте глобальные переменные, чтобы в памяти существовало только несколько объектов.